Core.async ‹! тупик канала

Почему Alpha останавливается раньше, когда я ожидаю, что она будет вести себя как Beta? Единственная разница между Alpha и Beta — это >! и put!, как указано ниже.

Альфа:

user=> (def q (chan))
#'user/q
user=> (def counter (atom 0))
#'user/counter
user=> (defn mg [event-queue]
  #_=>      (go-loop [event (<! event-queue)]
  #_=>       (swap! counter inc)
  #_=>       (when (< @counter 4)
  #_=>         (println "counter: " @counter)
  #_=>         (>! event-queue {:a @counter})      ;; Here's the only difference
  #_=>         (println "event: " event)
  #_=>         (recur (<! event-queue)))))
#'user/mg
user=> (mg q)
#object[clojure.core.async.impl.channels.ManyToManyChannel 0x3a1ffd56 "clojure.core.async.impl.channels.ManyToManyChannel@3a1ffd56"]
user=> (put! q "hi")
counter:  true
1
user=>

Бета:

user=> (def q (chan))
#'user/q
user=> (def counter (atom 0))
#'user/counter
user=> (defn mg [event-queue]
  #_=>   (go-loop [event (<! event-queue)]
  #_=>    (swap! counter inc)
  #_=>    (when (< @counter 4)
  #_=>      (println "counter: " @counter)
  #_=>      (put! event-queue {:a @counter})      ;; Here's the only difference
  #_=>      (println "event: " event)
  #_=>      (recur (<! event-queue)))))
#'user/mg
user=> (mg q)
#object[clojure.core.async.impl.channels.ManyToManyChannel 0x72c9b65a "clojure.core.async.impl.channels.ManyToManyChannel@72c9b65a"]
user=> (put! q "hi")
true
counter:  1
event:  hi
counter:  2
event:  {:a 1}
counter:  3
event:  {:a 2}
user=> 

Также интересно, что после выполнения Alpha канал #'user/q был правильно поставлен в очередь:

user=> (take! q println)
event:  hi
{:a 1}
nil
user=> 

Одни и те же результаты происходят как в Clojure, так и в Clojurescript. Это какой-то тупик или так и должно быть?


person Nick    schedule 20.11.2015    source источник


Ответы (1)


Это ожидаемо.

Канал q создается без буфера, поэтому при размещении значения с помощью >!, он заблокирует (припаркует) go-loop до тех пор, пока другой поток не будет готов использовать значение с <!.

Один из способов обойти это — дать q 1-слотовый буфер с (def q (chan 1)). Буфер позволяет разместить в канале 1 значение, не блокируя отправителя.

Бета-версия ведет себя иначе, потому что put! является асинхронным относительно. вызывающая сторона -- она ​​использует отдельный поток для размещения нового значения в канале. Это позволяет избежать блокировки текущего go-loop, позволяя читать канал и продолжать работу.

person lnmx    schedule 20.11.2015
comment
В качестве дополнения, put! можно использовать вне go-loop, чтобы поместить вещи внутрь канала, то есть внутри обратного вызова. Это намного эффективнее, чем создание множества маленьких go-loop. - person Ray H; 20.11.2015