跳轉到內容

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-linewrite-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 計算器 bcwrite-buffer 函式寫入 myout 管道,該管道透過 bcin 被 bc 讀取。bc 的輸出透過 bcout 指向,並由 newLISP 使用read-line 讀取。

connecting newLISP pipes to another process

(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!
華夏公益教科書