注意:这个问题与我的问题相当接近,但我至少可以对提供的解决方案使用一些工作示例,也许 ZeroMQ 会带来一些我不知道的魔力。
目前我对阻塞 ZeroMQ 调用的异常做出反应,如下所示:
1 2 3 4 5 6 7 8
|
try {
zmq::poll(&items, number, timeout);
} catch (zmq::error_t &ex) {
if (ex.num() != EINTR) {
throw;
}
}
…
|
我的意图是:重新抛出所有捕获的异常,但那些由中断的系统调用触发的异常,我通常可以忽略(例如 SIGPROF)并重新启动 zmq::poll。
如果是 SIGINT (CTRL-C),我想以不同的方式进行(例如,也重新抛出或终止循环)。
目前我最好的选择是安装一个监听 SIGINT 的信号处理程序,但由于 ZeroMQ 自己捕获信号,所以我更喜欢更复杂的方法。
- 我浏览了代码,除了 z/OS 操作系统之外,ZMQ 似乎并没有真正捕捉到信号。 EINTR 是 errno 的值之一,表示底层系统调用失败(并且可能会重试。) NEWS 文件中的注释似乎表明 ZMQ 停止捕获信号,因为他们无法做到以一种与自己期望捕获 SIGINT 的其他软件兼容的方式。
-
顺便说一句——快速测试:安装 SIGINT 处理程序时检查 signal() 的返回值是 SIG_DFL 还是 SIG_IGN。如果是这样,这强烈表明 ZMQ 没有安装自己的处理程序。
-
请问在这种情况下,信号的来源是什么?信号是进程或线程之间通信的一种方式。 ZeroMQ 也是一种在进程或线程之间进行通信的方式。坚持一种方式要干净得多,否则你会遇到这样的问题。如果信号是你的,那么我会用 ZMQ 套接字替换它们。如果信号的来源是外部的且不可避免,那是不幸的; @ScottM 的建议听起来很明智。
-
@bazza:信号类型错误;在这种情况下,这是一个超载的术语。 OP 表示 Unix 信号,即异常情况,例如 SIGNINT、SIGSEGV 等。这些不用于跨线程通信。
-
@ScottM我知道OP指的是Unix信号,它们用于信息通信。 SIGINT 和大多数其他信号的含义只有在您不安装处理程序的情况下才具有非任意性。而且,您是否建议您不能使用特定于线程的信号掩码?无论如何,使用单独的 ZMQ 套接字作为命令通道,而不是使用信号(例如 SIGINT)在 ZMQ 的 Actor 模型框架之上施加破坏性命令基础设施要容易得多。 OP 正在尝试(不必要地)将 SIGINT 用于此目的。
-
@bazza:我不是试图使用信号作为一种通信方式,但我想对 CTRL-C 做出适当的反应:在这种情况下 EINTR 表示”中止”,在所有其他情况下我想忽略它并重新启动中止命令(在我的情况下为 zmq::poll )。所以换句话说:我只想对 CTRL-C 做出反应,最好不必安装信号处理程序(因为我正在研究一个库并且不想修改一般的信号处理策略)
-
你好@frans,如果你的库没有控制信号处理策略,那么你需要应用程序不屏蔽 SIGINT。您仍然会限制应用程序对信号的使用。此外,您必须在代码中的任何地方(zmq_recv、zmq_send 等)处理 EINTR,而不仅仅是在 zmq_poll 上(信号是异步的,它们不会等待您对 zmq_poll 的调用)。如果您的”中止”命令是通过包含在被轮询的套接字集中的 ZMQ 套接字传递的消息,那就更好了。您也可以在投票中包含 stdin 以在按键时中止。
-
@frans,@bazza:您将遇到的主要问题是从信号处理程序中抛出异常。 EINTR 只是表示系统调用被中断。这并不一定意味着执行了 signal 处理程序——您仍然必须捕获 SIGINT,可能会调整某些状态(希望是原子的),然后引发 C 异常。不建议在 signal 处理程序中引发异常,并且非常未定义。基本上,您的方法不太可能可行。
-
@frans:不要尝试从 Unix 或 Linux signal 处理程序内部引发 C 异常。严重地。您可以在 Windows 上执行此操作,因为结构化异常处理程序 (SEH) 机制旨在执行此操作。 Unix signal 不是。
-
@ScottM,frans 根本不想使用信号处理程序(请参阅上面 frans 的最新评论),我主张完全避免使用信号来达到所需的结果。 Frans 的代码片段不在信号处理程序中,它位于循环的顶部。但是,是的,一般来说,从信号处理程序中抛出东西并不是一个好主意。
-
@bazza:我意识到@frans 想要避免不可避免的情况,但对于 SIGINT 处理来说却无法避免。我的建议(警告)是不要尝试从信号处理程序内部抛出异常。 ZMQ 在这个用例中没有帮助。 @fran 的另一个问题是 signal 与 C 异常混为一谈;它们是两种截然不同且不兼容的功能。
-
@frans:它看起来不像 libzmq 捕获 SIGINT,所以你当前的解决方案看起来是最好的解决方案。
如果我正确阅读了原始发帖者的问题,@frans 会询问是否有办法重新抛出某些异常,其中 C 异常包含 EINTR 错误代码,但某些信号生成的异常除外。 @frans 目前有一个 SIGINT 信号处理程序,想知道是否有更清洁的方法。
提出了两个不同的问题,关于 POSIX signal() 处理及其与 C 异常的交互:
-
POSIX 信号与 C 异常处理无关。
-
zmq::error_t 是由 zmq::poll() 作为返回 EINTR 的系统调用的结果生成的 C 异常。
TL;DR 回答:不,没有更清洁的方法。
libzmq 似乎没有安装自己的信号处理程序,但如果底层系统调用被中断(即,poll() 返回 -1,errno 复制zmq::error_t 异常。)这可能意味着 POSIX 信号可能已被传递并且特定于进程的处理程序运行,但还有其他原因。
POSIX/Unix/Linux/BSD signal() 是一种操作系统工具,用于指示内核中发生了异常情况。进程可以选择安装自己的处理程序以从这种情况中恢复,例如,SIGINT 和 SIGQUIT 处理程序来关闭文件描述符,进行各种类型的清理等。这些处理程序与 C 异常处理没有任何关系。
其他警告:不要从 POSIX/Unix/Linux/BSD 信号处理程序中抛出 C 异常。这在此 SO 主题中之前已讨论过。
我遇到了一个类似的问题,并且盯着这个问题及其答案看了很长时间,希望如果我足够努力的话,一个解决方案会神奇地出现。
最后,我采用了本文中详述的方法。
底线是我们需要一个基于 ppoll 或更确切地说是 pselect 的 ZMQ 轮询机制(我实现了一个,但我仍然需要将它放在一个单独的库中)。这些 p- 变体将信号掩码作为额外参数。他们设置信号掩码,运行常规 poll/select,然后将信号掩码重置为之前的状态。这意味着在 ppoll/pselect 之外,您可以阻止所有您期望的信号(阻止它们会导致它们被操作系统排队,它们不会丢失),然后仅在 poll/ 期间解除阻止它们select.
这实际上是在轮询器中添加一个”信号outlets”。轮询器将返回(使用 0)当它从实际套接字获得信号时或(使用 -1)当它被您允许使用信号掩码中断的信号中断时。您可以使用信号处理程序来设置一些标志。
现在,在您的 poll 调用之后,当您通常通过现在可读/可写的套接字时,您首先检查您从信号处理程序中设置的标志。信号在 ppoll/pselect 之外的任何地方都被阻塞,因此您可以肯定地知道,除了这里之外,您无需在其他任何地方检查此标志。这允许对 EINTR 进行非常干净、紧凑和稳健的处理。
一些注意事项:为此,您不能使用任何其他阻塞调用,因此您所有的 send 和 recv 都应该是非阻塞的(ZMQ_DONTWAIT 标志)。
另一件事是您已经提到的:在库的情况下,用户应该可以自由安装自己的信号处理程序。因此,如果他们想以稳健的方式使用您的库,您可能需要指导用户如何以这种方式正确处理信号。我认为如果您向用户公开标志(或翻转标志的函数)以便他们可以从自己的信号处理程序中调用它应该是可行的。
好的,所以有三种方法,一种简单,两种麻烦
干净
简单的方法是这样运行的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
zmq ::socket_t inSock ;
zmq ::pollitem_t pollOnThese [2];
int quitLoop = 0;
<code to connect inSock to something>
pollOnThese[0].socket = NULL; // This item is not polling a ZMQ socket pollOnThese[0].fd = 0; // Poll on stdin. 0 is the fd for stdin pollOnThese[0].event = ZMQ_POLLIN;
pollOnThese[1].socket = &inSock; // This item polls inSock pollOnThese[1].fd = 0; // This field is ignored because socket isn’t NULL pollOnThese[1].event = ZMQ_POLLIN;
while (!quitLoop) { zmq::poll(pollOnThese,2);
if (pollOnThese[0].revents == ZMQ_POLLIN) { // A key has been pressed, read it char c; read(0, &c, 1);
if (c == ‘c’) { quitloop = 1; } } if (pollOnThese[1].revent == ZMQ_POLLIN) { // Handle inSock as required } } |
当然这意味着你的”abort”不再是用户按下 CTRL-C,他们只是按下键 //’c//’,或者实际上任何可以写入这个标准输入的程序都可以发送一个 //’c //’。或者,您也可以添加另一个 ZMQ 套接字作为命令通道。这里根本没有对信号的容忍度,但我一直发现将信号的使用与 Actor 模型编程混合使用是一件非常尴尬的事情。见下文。
凌乱
使用信号的混乱方式看起来像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
|
zmq ::socket_t inSock ;
zmq ::pollitem_t pollOnThis ;
int quitLoop = 0;
<code to connect inSock to something>
<install a signal handler that handles SIGINT by setting quitLoop to 1>
pollOnThis.socket = &inSock; // This item polls inSock pollOnThis.fd = 0; // This field is ignored because socket isn’t NULL pollOnThis.event = ZMQ_POLLIN;
while (!quitLoop) { try { zmq::poll(&pollOnThis, 1); } catch (zmq::error_t &ex) { if (ex.num() != EINTR) { throw; } }
if (pollOnThis.revent == ZMQ_POLLIN && !quitLoop) { // Handle inSock as required bool keepReading = true; do { try { inSock.recv(&message) keepReading = false; } catch (zmq::error_t &ex) { if (ex.num() != EINTR) { throw; } else { // We also may want to test quitFlag here because the signal, // being asynchronous, may have been delivered mid recv() // and the handler would have set quitFlag. if (quitFlag) { // Abort keepReading = false; } else { // Some other signal interrupted things // What to do? Poll has said a message can be read without blocking. // But does EINTR mean that that meassage has been partially read? // Has one of the myriad of system calls that underpin recv() aborted? // Is the recv() restartable? Documentation doesn’t say. // Have a go anyway. keepReading = true; } } // if } // catch } while (keepReading);
<repeat this loop for every single zmq:: recv and zmq::send> } } |
混合(也很混乱)
在此处的 ZeroMQ 指南文档中,他们通过让信号处理程序写入管道并将管道包含在 zmq_poll 中,就像我在上面使用标准输入所做的那样,它们有点融合了这两种想法。
这是将异步信号转换为同步事件的常用技巧。
但是根本没有提示是否可以重新启动任何 zmq 例程。他们只是将其用作启动干净关闭的一种方式,已放弃任何正在进行的 recv() 或 send()。如果为了彻底中止,您需要完成一系列 recv() 和 send() 例程,那么如果正在使用信号,则无法保证这是可能的。
如果我错了,请原谅我,但感觉就像你正在重新利用 SIGINT 的意义,而不是”立即终止整个程序”。如果是这样,您可能希望稍后恢复您的通信循环。在这种情况下,谁知道在您的信号到达后是否有任何 recv() 或 send() 调用可以恢复。 zmq 将使用一大堆系统调用。它们中的许多在某些情况下是不可重新启动的,并且不知道 ZMQ 是如何使用这些调用的(缺少阅读它们的源代码)。
结论
基本上我会完全避免使用信号,尤其是当您不想安装自己的处理程序时。通过使用标准输入或管道或另一个 ZMQ 套接字(或实际上所有三个)作为包含在 zmq_poll() 中的”中止”通道,您将提供一种简单有效的方法来中止循环,并且不会产生任何并发症从它的使用。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/268837.html