什么时候通道会在一个线程继续被丢弃时被丢弃?

时间:2015-06-24 09:08:16

标签: clojure core.async

考虑从example walkthrough of core.async获取的以下代码:

(let [c1 (chan)
      c2 (chan)]
   (thread 
      (while true
         (let [[v ch] (alts!! [c1 c2])]
              (println "Read" v "from" ch))))
   (>!! c1 "hi")
   (>!! c2 "there"))

我的假设是线程引用了两个通道c1c2,并且基本上会一直运行,试图从任何永远不会出现的值中获取值。因此,通道也不会被垃圾收集,线程也不会终止。即使我们明确地close!频道,线程仍将继续。我的结论是正确的还是我错过了什么?

我在问,因为我正试图找到一种方法,让我可以成功地测试这样的core.async代码与这样一个无休止地运行的消费者。我目前的尝试看起来像这样:

(let [c1 (chan)
      c2 (chan)]
    (go 
        (>!! c1 "hi")
        (>!! c2 "there"))
    (async/thread
      (loop [[v ch] (alts!! [c1 c2])]
        (println "Read" v "from" ch)
        (when-let [[nv nch] (alts!! [c1 c2])]
          (if nv
             (recur [nv nch])
             :done)))))

这会返回一个结果通道(来自thread),我想阻止它取:done值,但我需要一种方法来关闭(至少有一个)通道。我可以返回两个频道c1, c2的列表以及thread然后close!返回的结果频道,例如之后c1检查结果频道,但这非常难看:

(let [c1 (chan)
      c2 (chan)]
    (go 
        (>!! c1 "hi")
        (>!! c2 "there"))
    [c1 c2 (async/thread
      (loop [[v ch] (alts!! [c1 c2])]
        (println "Read" v "from" ch)
        (when-let [[nv nch] (alts!! [c1 c2])]
          (if nv
              (recur [nv nch])
              :done))))])
=> [#<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@60eb5def> #<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@7c64279e> #<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@136535df>]
   Read hi from #<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@60eb5def>
   Read there from #<ManyToManyChannel clojure.core.async.impl.channels.ManyToManyChannel@7c64279e>

(let [[c1 c2 resultchan] *1]
  (close! c1)
  (<!! resultchan))
=>:done

或者,我可能会发送一个特殊的“通信结束”值,然后我可以在接收方检查。

最佳做法是什么样的?

2 个答案:

答案 0 :(得分:1)

我不知道是否有针对这种特殊情况的最佳做法。这是我解决问题的方法。我认为这很简单。

(defn alts-while-open [f & chans]
   (let [a-chans (atom (set chans))]
     (go (while (< 0 (count @a-chans))
           (println "iteration started : " (vec @a-chans))
           (let [[v ch] (alts! (vec @a-chans))]
             (if v
               (f v ch)
               (swap! a-chans #(disj % ch))))))))

函数f在alts的结果上执行!渠道是开放的。我在这里保留了一组带有开放通道的原子。一旦我找到一个已关闭的频道,我将其从此集中删除。如果没有更多打开的频道,则while循环停止。你可以运行它:

(def c1 (chan))
(def c2 (chan))
(def c3 (chan))
(alts-while-open (fn [v ch] (println v)) c1 c2 c3)

现在当某些内容被写入任何这些频道时,它就会被打印出来。您可以看到新的迭代在此之后开始。关闭通道后,您可以看到迭代开始但通道向量减少了。关闭所有通道后,while循环停止。

很难回答是否使用关闭的问题!函数或其他一些通知机制来停止循环。我认为这取决于具体情况。如果没有任何复杂的处理和#34;通信终止&#34;我会关闭!这个频道。如果有更复杂的逻辑 - 例如有成功的沟通结束&#34;并且失败&#34;沟通结束&#34;选项,我想以不同的方式处理它们,然后我宁愿发送一条特殊的信息。像

这样的东西
[:end-of-communication :success]

答案 1 :(得分:0)

当您不发送简单值时,发送特殊end-of-communication的想法不起作用,因为您无法保证按照您的顺序放置和接受值打算让他们拥有。

下一个想法是发送者和接收者都事先知道要处理的值的数量,例如像这样:

user> (<!!
        (let [c1 (chan)
              values ["hi" "there"]
              vcount (count values)]
           (doseq [value values]
             (thread
                  (>!! c1 value)))
           (thread
               (loop [recvalue (<!! c1)
                      reccount 1]
                  (println "Read" recvalue)
                  (if (= reccount vcount)
                      (do (close! c1)
                          :done)
                      (recur (<!! c1) (inc reccount)))))))
Read hi
Read there
:done

这原则上有效,但有明显的缺点,你必须在设置发送和接收过程之前就金额达成一致意见,以及不明显的缺点,即如果在发送方出现问题,你可以&#39; ll最终会在接收端无休止地等待(假设发送方做的不仅仅是将值放到通道上)。

我得出的结论是,使这个可靠的唯一方法是使用timeout频道,如下所示:

(<!! (let [c1 (chan)
           tchan (timeout 1000) 
           values ["hi" "there"]]
       (doseq [value values]
         (thread
           (>!! c1 value)))
       (thread
          (loop [[recvalue rchan] (alts!! [c1 tchan])
                 timeoutchan tchan]
            (if (= rchan timeoutchan)
                (do (close! c1)
                    :done)
                (do (println "Read" recvalue)
                    (let [newtimeout (timeout 1000)]
                        (recur (alts!! [c1 newtimeout])
                               newtimeout))))))

Read hi
Read there
:done