【问题标题】:Is Clojure's "send" asynchronous?Clojure 的“发送”是异步的吗?
【发布时间】:2011-08-05 20:38:56
【问题描述】:

我正在使用 Java 的 new I/O package 为 Clojure 编写一个简单的网络框架。它管理着一个“选择器代理”池,每个代理都拥有一个Selector。

我为选择器代理定义了一个dispatch 操作。此操作会阻止对 selector.select() 的调用。当它返回时,选择器代理迭代选定的键并执行 I/O。当 I/O 完成时,选择器代理使用send-off 向自己发送dispatch 操作,有效地循环调用selector.select()。

当我想添加新频道或更改频道的interest ops 时,我会向选择器代理发送适当的操作,然后取消阻止选择器(它在select() 上被阻止,记得吗?)。这确保了选择器代理中的(send-off selector-agent dispatch) 在(send selector-agent add-channel channel) 之后执行。


我认为这将是防弹的,因为对send-off 的调用是在选择器唤醒之前执行的,因此,在选择器代理向自身发送dispatch 操作之前。但是,这会产生不一致的行为。有时,dispatch 操作首先发生,有时则不会。

我的理解是,不能保证代理在来自多个线程时按照它们发送的确切顺序执行操作(即,就排队操作而言,send 和 send-off 不是同步的)。

这对吗?

【问题讨论】:

  • 如果你想在 Clojure 中建立 NIO 网络,我通常会推荐优秀的 Aleph 库 (github.com/ztellman/aleph),而不是自己编写......有什么理由不能使用它吗?
  • @mikera:有限状态机 (FSM) 是异步服务器的一个非常好的设计。我喜欢使用 Clojure 代理开发有限状态机是多么容易,并试图弄清楚我是否可以使用 Clojure 代理来实现网络协议(特别是 FTP,它需要为单个客户端操作两个连接)。这不是用于生产开发,而是对 Clojure 代理的实验 :-)

标签: clojure


【解决方案1】:

你完全正确。来自同一线程的操作将按照提交的顺序执行。但是您不能对来自不同线程的操作的执行顺序做出任何假设。

【讨论】:

  • 但这正是我的意思,它们不按照提交的顺序执行。
【解决方案2】:

send 和 send off 是为异步状态更改而构建的。 如果您需要同步更新,那么 atom 可能是您最好的工具。

由于您需要保留请求的顺序,因此您可能必须使用并发对象(原子)中的另一个数据结构来同步更新。将持久队列放在原子内并让所有线程同步添加到该队列中,而您的消费者同步从中提取条目可能会起作用。

这里是超级简短的决策图:

  • 多个和同步:使用一个引用
  • 异步一:使用代理
  • 异步多个:dosync 中的代理
  • 同步且只有一个:使用代理。

【讨论】:

  • 我的问题标题具有误导性。我正在使用代理因为我需要异步状态更改。我的问题与代理上的操作排队是异步的:当send 和send-off 返回时,该操作实际上还没有排队。
  • 感谢您的解释。我会尝试解决这个问题:)
  • 我将在选择代理中查看“在再次阻塞之前要更新的内容”的参考。但是,我宁愿有一个同步的send,因为代理本身已经作为一个操作积压。
  • 我实现了队列解决方案。除了成为整个代理系统的巨大旁路之外,它还无法工作。依赖“wakeup()”调用的整个概念是行不通的。我在 Clojure 邮件列表上发布了这个问题以获得一些额外的帮助。感谢回复!
【解决方案3】:

send 和send-off 保证动作将在单个线程中按照发送顺序放置在代理的队列中。更新代理的队列是同步发生的。

我希望您有一个简单的竞争条件,尽管我无法从描述中识别它。

【讨论】:

  • 我猜第一句话的关键部分是“单线程”。我从 1) 代理本身发送代理操作,并且; 2) 另一个代理。有问题的是这两个发送的顺序。 println 语句确认没有竞争条件(第二个代理总是首先发送)。但是,事实证明,无论如何,此更新存在更高级别的竞争条件。诅咒可变对象!
猜你喜欢
  • 2019-11-30
  • 1970-01-01
  • 1970-01-01
  • 2016-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多