我正在为一个线程实现一个包含消息的队列机制。该队列使用LinkedBlockingQueue
中的java.util.concurrent
构建。我想要实现的是以下内容。
Thread with mailbox:
defn work:
* do some stuff
* Get the head of the queue (a message):
- if it is "hello":
<do some stuff>
<recur work fn>
- if it is "bye":
<do some stuff>
- if it is none of the above, add the message to the back of queue
and restart from "Get the head of the queue"
* <reaching this point implies terminating the thread>
我尝试实现的第一个想法是使用围绕* Get the head of the queue
的循环,使用条件检查消息并将其添加到:else
分支中的队列中,如果它不匹配任何条款。这样做的缺点是,在使用recur
时,在cond
子句的任何主体中调用recur
将始终重复循环(例如,在hello
中} case)表示重复该函数(即work
)。所以这不是一个选择。另一个缺点是,如果此类消息需要很长时间才能到达,线程将无限期地旋转并占用资源。
我的下一个想法(但尚未实施)正在使用未来。该计划如下。
* Get all the matches I have to match (i.e., "hello" and "bye")
* Start a future and pass it the list of messages:
* While the queue does not contain any of the messages
recur
* when found, return the first element that matches.
* Wait for the future to deliver.
* if it is "hello":
<do some stuff>
<recur work fn>
if it is "bye":
<do some stuff>
当这样做时,我几乎得到了我想要的东西:
"hello"
或"bye"
个阻止,直到我拥有其中任何一个。future
中
每次我评估cond
时都有很好的副作用
我确定我有一个匹配的消息,不必担心重试。我真正想要的一件事,但无法想象如何实现,就是在这种情况下的未来不会旋转。就目前而言,它会不断消耗掉流经队列的宝贵CPU资源,而从未接收到它正在寻找的消息之一可能是完全正常的。
也许放弃LinkedBlockedQueue
并将其换成具有方法的数据结构是有意义的,比如,getEither(List<E> oneOfThese)
会阻塞,直到其中一个元素可用为止。
我有另一种想法,这是我在Java中可能做到的一种方式,如果队列中没有任何元素在队列中,则在队列上进行上述getEither()
操作,调用wait()
。当另一个线程将消息放入队列时,我可以调用notify()
,以便每个线程将根据他想要的消息列表检查队列。
示例
以下代码运行正常。但是,它有旋转问题。这基本上是我想要实现的一个非常基本的例子。
(def queue (ref '()))
(defn contains-element [elements collection]
(some (zipmap elements (repeat true)) collection))
(defn has-element
[col e]
(some #(= e %) col))
(defn find-first
[f coll]
(first (filter f coll)))
; This function is blocking, which is what I want.
; However, it spins and thus used a LOT of cpu,
; whit is *not* what I want..
(defn get-either
[getthese queue]
(dosync
(let [match (first (filter #(has-element getthese %) @queue))
newlist (filter #(not= match %) @queue)]
(if (not (nil? match))
(do (ref-set queue newlist)
match)
(Thread/sleep 500)
(recur)))))
(defn somethread
[iwantthese]
(let [element (get-either iwantthese queue)
wanted (filter #(not= % element) iwantthese)]
(println (str "I got " element))
(Thread/sleep 500)
(recur wanted)))
(defn test
[]
(.start (Thread. (fn [] (somethread '(3 4 5)))))
(dosync (alter queue #(cons 1 %)))
(println "Main: added 1")
(Thread/sleep 1000)
(dosync (alter queue #(cons 2 %)))
(println "Main: added 2")
(Thread/sleep 1000)
(dosync (alter queue #(cons 3 %)))
(println "Main: added 3")
(Thread/sleep 1000)
(dosync (alter queue #(cons 4 %)))
(println "Main: added 4")
(Thread/sleep 1000)
(dosync (alter queue #(cons 5 %)))
(println "Main: added 5")
)
任何提示?
(如果有人注意到,是的,这就像演员一样,目的是为了学术目的而在Clojure中实现)
答案 0 :(得分:1)
你需要2个队列而不是1个队列:传入队列和#34;死信&#34;队列。
core.async/<!
或使用代理)。 请参阅下面的两个实现。
<强>剂强>
特工与演员非常相似,只有&#34;区别在于您向演员发送数据/消息,但您将功能发送给代理。可能的实现方式是:
(defn create-actor [behaviour]
(agent {:dead-queue []
:behaviour behaviour}))
dead-queue将包含与任何子句都不匹配的消息。这基本上就是你的队列结束&#34;。
behaviour
应该是match-fn到fn的一些map / vector来运行。在我的特定实现中,我选择了一个映射,其中键是要匹配的元素,值是新项匹配时要运行的fn:
(def actor (create-actor {3 println
4 (partial println "Got a ")
5 #(println "Got a " %)}))
您可能需要更复杂的behaviour
数据结构。唯一重要的是知道元素是否被处理,所以你知道元素是否必须转到死队列。
向演员发送消息:
(defn push [actor message]
(send actor
(fn [state new-message]
(if-let [f (get-in state [:behaviour new-message])]
(do
(f new-message)
state)
(update-in state [:dead-queue] conj new-message)))
message))
因此,如果behaviour
上存在匹配项,则会立即处理该消息。如果不是,则将其存储在死队列中。如果您希望behaviours
不是纯函数,则可以在处理新消息后尝试匹配/处理死队列中的所有消息。在此示例实现中,这是不可能的。
我们可以更改actor的behaviour
以使死队列中的消息有机会被处理:
(defn change-behaviour [actor behaviour]
(send actor
(fn [state new-behaviour]
(let [to-process (filter new-behaviour (:dead-queue state))
new-dead-queue (vec (remove (set to-process) (:dead-queue state)))]
(doseq [old-message to-process
:let [f (get new-behaviour old-message)]]
(f old-message))
{:behaviour new-behaviour
:dead-queue new-dead-queue}))
conds))
使用它的一个例子:
(push actor 4)
(push actor 18)
(push actor 1)
(push actor 18)
(push actor 5)
(change-behaviour actor {18 (partial println "There was an")})
基于core.async的相同解决方案:
(defn create-actor [behaviour]
(let [queue (async/chan)]
(async/go-loop [dead-queue []
behaviour behaviour]
(let [[type val] (async/<! queue)]
(if (= type :data)
(if-let [f (get behaviour val)]
(do
(f val)
(recur dead-queue behaviour))
(recur (conj dead-queue val) behaviour))
(let [to-process (filter val dead-queue)
new-dead-queue (vec (remove (set to-process) dead-queue))]
(doseq [old-msg to-process
:let [f (get val old-msg)]]
(f old-msg))
(recur new-dead-queue val)))))
queue))
(defn push [actor message]
(async/go
(async/>! actor [:data message])))
(defn change-behaviour [actor behaviour]
(async/go
(async/>! actor [:behaviour behaviour])))
答案 1 :(得分:0)
您是否考虑过使用core.async?它以轻量级的方式提供您所需的功能。