ZeroMQ:如何处理 ZeroMQ 节点中与消息无关的异步事件?

ZeroMQ: How to handle non-message-related, asynchronous events in a ZeroMQ node?

假设我有一个带有 ZeroMQ 接口的节点(进程、线程等),比方说一个 REP 套接字。这意味着我有一个无限主循环,它在 zmq_recvzmq_poll 函数中休眠。

现在该节点还应该从另一个异步事件接收数据。例如,想象一下键盘按钮按下事件或切换 GPIO 引脚或计时器到期。在等待这些事件时,执行也必须休眠。

应该如何编写主循环,使其在任一类型的事件(即消息接收和异步事件发生)上唤醒?

我可以想到两个解决方案:

首先,一个轮询循环,其中两个事件都以非阻塞方式检查,我 运行 每几毫秒循环一次。这在处理负载方面似乎不理想。

其次,我将两个事件的阻塞代码移动到单独的线程中。相反,在主循环中,我睡在一个信号量(或条件变量)上,它​​由任一事件的发生发布。这就是我在传统应用程序中采用的方式,但是 Pieter Hintjens 编写的 ZeroMQ Guide 非常明确地使用信号量 not

Stay away from the classic concurrency mechanisms like as mutexes, critical sections, semaphores, etc. These are an anti-pattern in ZeroMQ applications.

那怎么办?这里的最佳做法是什么?

How should the main loop be written, such that it wakes up on either type of event, namely the reception of a message and the occurrence of the asynchronous event ?

您几乎已经回答了自己。并发事件循环是一个问题。 ZeroMQ 允许处理异步和同步检查的消息队列。

鉴于您的控制逻辑需要一些额外的与时间相关的决策,任何 进入任何阻塞状态都是非常糟糕的主意(即使 posts 中的流行媒体和“help”-文件呈现了几乎所有带有阻塞状态 SLOC 的琐碎代码示例,用于“explanations”。No.No.No.

永远不要失去你的控制权。第一个阻塞状态将您的代码从其自己控制的域中删除。号号号

What's the best practice here ?

即使你的控制策略是复杂的、细粒度的,无论如何,最好使用显式控制的时序部分,在那里你可以首先检测,使用{非阻塞|受控持续时间阻塞 }-调用 .poll(),用于显式 { ACK'd | NACK' } 任何消息到达的存在,以及 next 您的处理策略可能会根据您的优先级决定如何处理检测到的事件组合,post-process - 持续时间和资源可用性状态 and/or 它们各自处理每个检测到的事件的相对性能 post - 处理工作负载仍在控制循环硬时间限制内。

这就是{软|硬}-实时系统工作。

ZeroMQ 提供了一些技巧来正确和智能地执行此操作(使用零复制机制,使用每个 Context() 实例的嵌入式 IO 线程池的性能/延迟调整选项,使用 几乎-零延迟inproc://传输-class,在可行的情况下避免老式并发花哨的东西……Martin SUSTRIK posted很多关于性能和更多细节)


奖金部分:

如果您的硬件资源似乎太弱而无法处理任何成熟的胖代理网络,仍然可以使用零 IO 线程将您的系统组成轻量级代理网络 Context()-s,通过 inproc://-s 的网格与系统的 War-Gaming-Control-Room[= 进行通信46=],在这里收集和维护态势感知,做出符合全球控制战略的决策,并从那里发布相应的战略、战术和作战命令,协调分配并沿着网格编组,以便在轻量级和内部进行最终执行任务专业化几乎-自主代理。

0MQ 风格的解决方案是使用第二个套接字将其他异步事件通知主线程。然后将两个套接字传递给 zmq_poll,当任一套接字中有可用消息时,您的主线程将收到通知。

例如,除了 REP 套接字之外,您的主线程还可以创建一个 PULL 套接字。它会将两者传递给 zmq_poll 然后阻止等待消息。当触发异步事件时,单独的线程可以将 PUSH 套接字连接到 PULL 并将事件数据传递给等待的主线程。

有关如何轮询多个套接字的详细信息,请参阅 zmq_poll docs

根据您的 OS,系统中的大多数事件都是通过一些文件描述符准备好读取而生成的。这通常是它在 Linux、Unix 等系统上的工作方式。例如,键盘输入通过 STDIN 输入。当然,任何描述的文件描述符都可以包含在 ZMQ 轮询中。

如果事件不是通过可在 ZMQ 轮询中使用的文件描述符引发的(例如 Windows 上的串行端口准备好读取),我通常使用线程将事件转换为通过 ZMQ 套接字发送的消息。效果很好。变得不可携带,但这是不可避免的。

GPIO 可能更难。如果它没有得到某个将 ISR 集成到 OS 的驱动程序堆栈中的驱动程序的支持,那么您将不得不轮询它。