UP | HOME

NOTE: racket/future 的限制與想像

1. 最開始的問題

在解決 sauron 要如何從定義跳轉到使用位置的時候,我讓 maintainer 之間互相通知依賴關係,由依賴方通知被依賴方。 這樣的程式雖然符合直覺,卻因為實作而受到限制。正如以下推文說到的問題

這些通知會被放到各個 thread 隱含的 channel 上,而這些會觸發排程器啟動 thread,然而在 racket thread 上並不會利用 multi-processor。 而使得這些 thread 被快速的換到同一個 core 上執行,大量的 context switching,拖垮了編輯器的反應。

2. 解方?

我只好去思考還有什麼能讓我繼續用熟悉的 Erlang process 抽象方式寫程式。

2.1. Thread group(不可行)

直覺的反應是用 thread group 限定 CPU 的分享。但在 racket 中,一個 thread 只能在啟動時指定其 group,因而限制了這個方案的可行性。 而且重點應該還是訊息的處理應該是平行的。

2.2. 封裝 Future(失敗)

於是我寫了以下的程式

(define (make-process ch)
  (future
    (lambda ()
      (let loop ()
        (match (async-channel-get ch)
          ...)
        (loop)))))

這段程式的問題是, async-channel-get 會 suspend 一個 future,於是它其實並沒有像我想像的那樣運作! 或許每次 async-channel-put 都配合一個 touch 是可行的,但是因為最後這個 future 沒有終止,所以程式要是最後想 touch 來結束是不可能的。 於是我似乎需要更好的方案。

2.3. Future thunk 回傳 Future?

我現在想的方式則是延續 async-channel-put 配合一個 touch 之後,讓 future 內部的函數再次回傳一個同樣的運算來表達無限迴圈

(struct process (fu ch))

(define (process-send pro msg)
  (async-channel-put (process-ch pro) msg)
  (touch (process-fu pro)))

(define (^friend my-name [ch (make-async-channel)])
  (process (future
            (thunk (match (async-channel-get ch)
                     [(list 'ping from)
                      (printf "ping from ~a~n" my-name)
                      (process-send from 'pong)
                      (^friend my-name ch)]
                     ['pong
                      (printf "pong from ~a~n" my-name)
                      (^friend my-name ch)])))
           ch))

(define bob (^friend "Bob"))
(define jack (^friend "Jack"))

(process-send bob (list 'ping jack))

這當然是可以簡單地令第一次的 process-send 運作,但要是呼叫第二次就會發現因為我們 touch 過了,而沒有儲存下這個新的運算位置因此會出錯。 進一步就會想到要是我們建立一個 hash 來儲存這個結果,應該就可以重複運算。

然而, future 是平行運算,於是不同的 future 完全可能在同一個時間上 process-send 到某一個 future 。 這時, hash 應該儲存哪一個運算結果呢?

所以我應該會需要一個 place 專門管理這件事,要求所有的訊息應該都要先通過這個 place 的 channel 之後才往各個 process 上分配。

3. 結論

我應該會在之後建立實驗性的程式庫去驗證這個想法,希望能用

Date: 2022-09-03 Sat 00:00
Author: Lîm Tsú-thuàn