newLISP 多任务介绍
以下函数允许您与操作系统交互
- ! 在操作系统中运行命令
- abort 停止所有生成的进程
- destroy 杀死一个进程
- exec 运行一个进程并从中读取或写入
- fork 启动一个 newLISP 子进程线程(Unix)
- pipe 创建用于进程间通信的管道
- process 启动一个子进程,重新映射标准 I/O 和标准错误
- semaphore 创建和控制信号量
- share 与其他进程和线程共享内存
- spawn 创建一个新的 newLISP 子进程
- sync 监控和同步生成的进程
- wait-pid 等待子进程结束
由于这些命令与您的操作系统交互,您应该参考平台特定问题的文档和限制。
! 运行系统命令并在控制台中显示结果。exec 函数执行类似的操作,但它会等待操作系统完成,然后将标准输出作为字符串列表返回,每行一个字符串
(exec "ls -Rat /usr | grep newlisp")
;->
("newlisp" "newlisp-edit" "newlispdoc" "newlisp" "newlisp.1"
"newlispdoc.1" "/usr/share/newlisp:"
"/usr/share/newlisp/guiserver:"
"/usr/share/newlisp/modules:" "/usr/share/newlisp/util:"
"newlisp.vim" "newlisp" "/usr/share/doc/newlisp:"
"newlisp_index.html" "newlisp_manual.html"
"/usr/share/doc/newlisp/guiserver:")
像往常一样,您将拥有所有乐趣来引用和双重引用以将您的命令传递给 shell。
使用exec,您的脚本将在命令完成之前等待,然后您的脚本继续执行。
(exec (string "du -s " (env "HOME") "/Desktop"))
您只会在命令完成时看到结果。
要与 newLISP 旁边运行的另一个进程进行交互,而不是等到进程完成,请使用process。参见 进程.
到目前为止,我们评估的所有 newLISP 表达式都按顺序运行,一个接一个,因此一个表达式必须完成评估,newLISP 才能开始下一个表达式。这通常没问题。但有时您希望启动一个表达式,然后在第一个表达式仍在评估时继续执行另一个表达式。或者您可能希望将一个大型作业分成多个较小的作业,也许可以利用计算机拥有的任何额外的处理器。newLISP 使用三个函数进行这种多任务处理:spawn 用于创建并行运行的新进程,sync 用于监控和完成它们,以及abort 用于在它们完成之前停止它们。
对于以下示例,我将使用这个简短的“脉冲”函数
(define (pulsar ch interval)
(for (i 1 20)
(print ch)
(sleep interval))
(println "I've finished"))
当您正常运行此函数时,您会看到打印了 20 个字符,每 interval 毫秒打印一个。此函数的执行会阻塞其他所有操作,您必须等待所有 20 个字符,或者使用 Control-C 停止执行。
要在此函数与当前函数并行运行的另一个进程中运行此函数,请使用spawn 函数。提供一个符号来保存表达式的结果,然后是将要评估的表达式
> (spawn 'r1 (pulsar "." 3000)) 2882 > .
函数返回的数字是进程 ID。现在,您可以在终端中继续使用 newLISP,而 pulsar 继续不断中断您。这非常烦人 - 点会一直出现在您的输入中间!
再启动几个
> (spawn 'r2 (pulsar "-" 5000)) 2883 > (spawn 'r3 (pulsar "!" 7000)) 2884 > (spawn 'r4 (pulsar "@" 9000)) 2885
要查看有多少个进程处于活动状态(并且尚未完成),请不带参数使用sync 函数
> (sync) (2885 2884 2883 2882)
如果要停止所有 pulsar 进程,请使用abort
(abort) true
在 MacOS X 上,尝试这个更有趣的版本,它使用驻留的语音
(define (pulsar w interval)
(for (i 1 20)
(! (string " say " w))
(sleep interval))
(println "I've finished"))
(spawn 'r1 (pulsar "spaghetti" 2000))
(spawn 'r2 (pulsar "pizza" 3000))
(spawn 'r3 (pulsar "parmesan" 5000))
sync 也允许您监控当前正在运行的进程。提供以毫秒为单位的值;newLISP 会等待那个时间,然后检查生成的进程是否已完成
> (spawn 'r1 (pulsar "." 3000)) 2888 > . > (sync 1000) nil
如果结果为 nil,则进程尚未完成。如果结果为 true,则所有进程都已完成。现在 - 并且只有在sync 函数运行并返回 true 之后 - 返回符号 r1 的值将设置为进程返回的值。对于 pulsar,这将是字符串“我已完成”。
I've finished > r1 nil > (sync 1000) true > r1 "I've finished" >
请注意,进程已完成 - 或更确切地说,它打印了其结束消息 - 但符号 r1 直到sync 函数执行并返回 true 时才被设置。这是因为sync 返回 true,而返回符号具有值,只有当所有生成的进程都已完成时。
如果您想等待所有进程完成,可以执行一个循环
(until (sync 1000))
每秒检查一次,看看进程是否已完成。
作为奖励,许多现代计算机都拥有多个处理器,并且您的脚本可能能够更快地运行,如果每个处理器都能将其精力集中在一个任务上。newLISP 将根据其所处硬件的任务调度和处理器调度留给操作系统来处理。
有一些用于操作进程的更底层的函数。这些函数不像上一节中描述的生成的进程技术那样方便或易于使用,但它们提供了一些附加功能,您可能有一天会发现这些功能有用。
您可以使用fork 在另一个进程中评估表达式。一旦进程启动,它不会将值返回给父进程,因此您必须考虑如何获取它的结果。以下是如何在单独的进程中计算质数并将输出保存到文件的方法
(define (isprime? n)
(if (= 1 (length (factor n)))
true))
(define (find-primes l h)
(for (x l h)
(if (isprime? x)
(push x results -1)))
results)
(fork (append-file "/Users/me/primes.txt"
(string "the result is: " (find-primes 500000 600000))))
在此,由fork 启动的新子进程知道如何查找质数,但是,与生成的进程不同,它不能将信息返回给其父进程以报告它找到了多少个质数。
进程可以共享信息。share 函数设置一个公共内存区域,就像一个公告板,所有进程都可以读写。后面的章节有一个关于share 的简单示例:参见 一个简单的 IRC 客户端。)
为了控制进程如何访问共享内存,newLISP 提供了semaphore 函数。
如果您希望分叉的线程相互通信,您将需要先做一些管道工作。使用pipe 设置通信通道,然后安排一个线程监听另一个线程。pipe 返回一个列表,其中包含对新进程间通信管道的读写句柄,然后您可以将这些句柄用作read-line 和write-line 函数的通道来读写。
(define (isprime? n)
(if (= 1 (length (factor n)))
true))
(define (find-primes l h)
(for (x l h)
(if (isprime? x)
(push x results -1)))
results)
(define (generate-primes channel)
(dolist (x (find-primes 100 300))
(write-line channel (string x)))) ; write a prime
(define (report-results channel)
(do-until (> (int i) 290)
(println (setq i (read-line channel))))) ; get next prime
(define (start)
(map set '(in out) (pipe)) ; do some plumbing
(set 'generator (fork (report-results in)))
(set 'reporter (fork (generate-primes out)))
(println "they've started"))
(start)
they've started 101 103 107 109 ...
(wait-pid generator)
(wait-pid reporter)
请注意,字符串“它们已启动”出现在任何质数打印之前,即使该println 表达式出现在线程启动之后。
wait-pid 函数等待由fork 启动的线程完成 - 当然,您不必立即执行此操作。
要启动一个与 newLISP 并行运行的新操作系统进程,请使用process。与fork 一样,您首先需要设置一些合适的管道,以便 newLISP 可以与该进程进行对话和监听,在以下示例中,该进程是 Unix 计算器 bc。write-buffer 函数写入 myout 管道,该管道通过 bcin 被 bc 读取。bc 的输出通过 bcout 指向,并由 newLISP 使用read-line 读取。
(map set '(bcin myout) (pipe)) ; set up the plumbing
(map set '(myin bcout) (pipe))
(process "/usr/bin/bc" bcin bcout) ; start the bc process
(set 'sum "123456789012345 * 123456789012345")
(write-buffer myout (string sum "\n"))
(set 'answer (read-line myin))
(println (string sum " = " answer))
123456789012345 * 123456789012345 = 15241578753238669120562399025
(write-buffer myout "quit\n") ; don't forget to quit!