Clojure

Go Block 的最佳實務

一般建議

以下是很容易做到的,用來傳送訊息而不必等待回覆

(go (>! c 42))

雖然 go block 很便宜,但並非完全免費。因此建議使用

(async/put! c 42)

go 最終只會呼叫 put!,所以實際上沒有缺點。

此外,如果程式碼是在回呼中呼叫,而且您想要尊重反壓力,則可以很容易地使用遞迴函數搭配 put! 來尊重反壓力。

(defn http-call
  "Makes an async call to a web browser"
  [url callback] ...)


(def urls [url1 url2 url3])

(defn load-urls
  "Spools the results of loading several urls onto a channel.
   does this without creating temporary channels or go blocks"
  [urls out-c]
  (http-call
    (first urls)
    (fn [response]
      (put! out-c response (fn [_] (load-urls (next urls) out-c))))))

(load-urls urls response-chan)

在此範例中,我們有一些不錯的乾淨互通操作程式碼,讓我們可以在應用程式中開始使用通道,而不用建立大量通道或 gos,只為了在建立後不久就將它們處置掉。

請記住,尊重反壓力很重要。core.async 的一般原則是不定界佇列很糟糕,而且未決的 put 數量有限(目前為 1024)。另一個選項是使用始終立即接受 put 的通道,例如 dropping-buffersliding-buffer

go 區塊中不受支援的結構與其他限制

go 巨集會在函式建立的邊界停止轉譯。這表示以下程式碼會無法編譯,或可能只會擲出一個執行時期錯誤,指出 <! 在 go 區塊外使用

(go (let [my-fn (fn [] (<! c))] (my-fn)))

這是需要記住的一件事,因為許多 Clojure 結構會在巨集中建立函式。以下是不會如預期般運作的程式碼範例

(go (map <! some-chan))
(go (for [x xs]
      (<! x)))

然而,其他 Clojure 結構,例如 doseq,並不會在內部配置封閉函式

; This works just fine
(go (doseq [c cs]
      (println (<! c)))

不幸的是,目前沒有好的方法可以知道給定的巨集是否會在 go 區塊中如預期般運作,除非查看原始碼或測試巨集產生的程式碼。

為什麼會這樣?

對於「為什麼 go 區塊轉譯會在函式建立時停止?」的最佳解釋基本上歸結為型別問題。檢視以下片段

(map str [1 2 3])

我們可以輕易看出這會產生一個字串的 seq,因為 str 的輸出型別是一個字串。那麼 async/<! 的回傳型別是什麼?在 go 區塊的語境中,它是一個從通道取得的物件。但 go 區塊必須將其轉譯為對 async/put! 的暫停呼叫。async/<! 的回傳型別實際上應該被視為類似於 Async<Object>Promise<Object> 的東西。因此 (map async/<! chans) 的結果類似於「一個待處理通道操作的 seq」,這完全沒有意義。

簡而言之,go 巨集無法在沒有大量工作的情況下執行這些操作。其他語言,例如 Erjang,允許透過轉譯整個 JVM 中的所有程式碼來建立此類結構。這是我們希望在 core.async 中避免的事情,因為它會使事情複雜化,並導致一個函式庫的邏輯感染整個 JVM 的程式碼。因此,我們只剩下實用的折衷方案,當看到 (fn [] …​) 時,轉譯就會停止。

原始作者:Timothy Baldridge