I'm writing a simple networking framework for Clojure using Java's new I/O package. It manages a pool of "selector agents", each of which holds a Selector.
I defined a dispatch
action to for the selector agent. This action blocks on a call to selector.select()
. When that returns, the selector agent iterates over the selected keys and performs I/O. When I/O is completed, the selector agent send's itself the dispatch
action using send-off
, effectively looping on calls to selector.select()
.
When I want to add a new channel or change a channel's interest ops, I send the selector agent the appropriate action and then unblock the selector (it's blocked on select()
, remember?). This ensures that (send-off selector-agent dispatch)
in the selector agent is exe开发者_开发技巧cuted after (send selector-agent add-channel channel)
.
I thought this would be bullet-proof since the call to send-off
is performed before the selector waking up, and thus, before the selector agent send itself the dispatch
action. However, this yields inconsistent behavior. Sometimes, the dispatch
action occurs first and sometimes it doesn't.
My understanding is that it's not guaranteed that agents execute actions in the exact order they were sent when they come from multiple threads (i.e. send
and send-off
are not synchronous as far as queuing actions is concerned).
Is this correct?
send
and send-off
guarantee that actions will be placed on the Agent's queue in the order they are sent, within a single thread. Updating the Agent's queue happens synchronously.
I expect you have a simple race condition, although I can't identify it from the description.
You are absolutely right. Actions coming from the same thread will be executed in the same order, as they were submitted. But you cannot make any assumptions about execution order of actions, that come from different threads.
send and send off are built for asynchronous state changes. if you need synchronous updates then atoms are likely your best tool.
since you need to preserve the order of requests you may have to use another data structure within a concurrency object (atom) can be syncrounoustly updated. It may work to put a persistent-queue inside an atom and have all your threads synchronousness add to that queue while your consumers synchronously pull entries from it.
here is the super brief decission chart:
- more than one and synchronous: use a ref
- asynchronous one: use an agent
- asynchronous more than one: agents within a dosync
- synchronous and only one: use an agent.
精彩评论