【问题标题】:Clojure async/go how to park blocking codeClojure async/go 如何停放阻塞代码
【发布时间】:2018-04-01 14:57:14
【问题描述】:

我使用一些 Java 库来发出非异步获取和发布请求。我曾经将此类请求包装到期货中,它为我解决了“等待问题”(我的意思是等待响应)

(defn unchangeable-lib-request [n]
  (Thread/sleep 1000)
  n)

(defn process [n]
  (let [res (atom [])]
    (dotimes [i n]
      (future (swap! res conj (unchangeable-lib-request i))))
    (loop []
      (if (> n (count @res))
        (recur)
        @res))))

(time (process 9))

;; "Elapsed time: 1000.639079 msecs"
;; => [8 7 5 6 4 3 2 1 0]

但是我需要创建数百个请求,这会产生性能问题。我发现了 core.async 和 go 块。但是如果我将 go-blocks 与这个库一起使用,它不会解决“等待问题”

(defn unchangeable-lib-request [n]
  (Thread/sleep 1000)
  n)

(defn process [n]
  (let [c (async/chan 10)]
    (dotimes [i n]
      (async/go
        (async/>! c (unchangeable-lib-request i))))
  (loop [result []]
    (if (> n (count result))
      (recur (conj result (async/<!! c)))
      result))))

(time (process 9))

;; "Elapsed time: 2001.770183 msecs"
;; => [0 4 1 6 7 2 5 3 8]

Go 块只能同时处理 8 个请求。是否有可能编写一些异步包装器来停放 go-block 并提供异步发出 100 个请求而不会相互阻塞的能力?

(defn process [n]
  (let [c (async/chan 10)]
    (dotimes [i n]
      (async/go
        (async/>! c (magic-async-parking-wrapper
                      (unchangeable-lib-request i))))
  (loop [result []]
    (if (> n (count result))
      (recur (conj result (async/<!! c)))
      result))))

(time (process 9))

;; "Elapsed time: 1003.2563 msecs"

我知道异步/线程,但这似乎与(未来...)相同。

有可能吗?

【问题讨论】:

  • 你读过this吗?
  • 你指的是什么?我在那里没有发现任何有用的东西。你的意思是使用 (Thread/sleep _) 不是一个好主意?所以,这就是重点。我已经有一个阻塞操作并且无法更改它。我需要用它做点什么

标签: multithreading concurrency clojure core.async


【解决方案1】:

我建议:

  • 使用期货创建线程,并让他们使用put!将结果从任何go块外部放回核心异步通道,例如:(future (put! chan (worker-function)))
  • 然后使用 go 块在该(单个)通道上等待,在获得结果时输入结果。

【讨论】:

  • 嗯,为什么是put! 而不是&gt;!!?我不完全理解两者之间的区别,但我认为put! 的意义在于,如果通道没有缓冲区空间,您可以进行更高级的错误处理。既然你只是想等待空间,我会期待&gt;!!。
  • 我的第一个实现和你的答案有什么区别?如果我现在想发出 100 个请求,程序将创建 100 个期货,它们将等待自己的请求直到响应。这不是异步的。我有什么不明白的地方?
  • @amalloy 不同之处在于如果通道c 没有空间,&gt;!! 将阻塞线程并等待机会将值放入c,但put! 将不要阻塞线程,并在有机会时异步放置一个值。但是是的,看来,在这种情况下没有区别
  • ˋput!ˋ 可以在 ˋgoˋ 块之外使用,而箭头函数不能。
  • @tarmes &gt;! 和 &lt;! 箭头函数不能在 go 块之外使用。但是&gt;!!和&lt;!!可以,但是会阻塞线程
【解决方案2】:

这是你使用clojure.core.async/pipeline-blocking的地方

(require '[clojure.core.async :as a :refer [chan pipeline-blocking]])


(let [output-chan (chan 100)
      input-chan (chan 1000)]
  (pipeline-blocking 4 ; parallelism knob
                     output-chan 
                     (map unchangeable-lib-request) 
                     input-chan)
  ;; Consume results from output-chan, put operations on input-chan
  [output-chan input-chan]
  )

这会产生 n 个(在本例中为 4)个线程,它们一直忙于执行 unchangeable-lib-request。

使用output-chan 的缓冲区大小来微调您希望提前发生多少请求。

使用input-chan 的缓冲区大小来微调您希望在没有反向传播的情况下调度多少请求(阻塞input-chan)。

【讨论】:

  • 耶!很好的解决方案,谢谢!但是这种方法和使用期货之间有性能差异吗?据我了解,将创建 N 个线程(=期货)来处理阻塞操作。仅使用 1-2 个带停放的线程就无法等待阻塞请求?
  • @Defake 不确定我是否理解您的问题,但您可以将4 更改为2 或1,然后将只有2 个线程或1 个。
  • @Defake 你打算如何处理停车?
  • 您可以根据需要添加反向通道。放入input-chan 类似{:request-data "whatever-the-java-thing-needs", :response (promise-chan)} 的请求。然后让unchangeable-lib-request 通过&gt;!! 将回复放在:response 上。然后做(go (let [resp (promise-chan)] (&gt;! input-chan {:request-data ..., :response resp}) (&lt;! resp))。该块将被停放,直到计算出结果。不过,请确保使用 dropping-buffer 代替 output-chan。
  • 示例程序运行 4 个线程,不多也不少。一旦您关闭input-chan,它们就会关闭。这很好; JVM 明智的。问题在于每个请求只有一个线程,因为它们会相互阻塞。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-04-02
  • 1970-01-01
  • 1970-01-01
  • 2011-09-13
  • 2016-01-18
  • 2017-08-17
相关资源
最近更新 更多