我建议通过与
select()vs 开始比较
poll()。Linux还提供
pselect()和
ppoll();和和(vs
和)的额外
const sigset_t *参数对每个“
p变量”具有相同的作用。如果您不使用信号,那么您就没有竞争的机会,因此,基本问题实际上是关于效率和易于编程的问题。
pselect()``ppoll()``select()``poll()
至于比赛:一旦开始使用信号(无论出于何种原因),您将了解到,信号处理程序通常应设置一个类型变量,
volatilesig_atomic_t以指示已检测到信号。造成这种情况的根本原因是,许多库调用都不是可重入的,并且可以在您处于此类例程“中间”时传递信号。例如,仅将消息打印到流样式的数据结构(例如
stdout(C)或
cout(C
++))会导致重新进入问题。
假设您有使用
volatile sig_atomic_tflag变量(可能是catch)的代码,
SIGINT如下所示(另请参见http://pubs.opengroup.org/onlinepubs/007904975/functions/sigaction.html):
volatile sig_atomic_t got_interrupted = 0;void caught_signal(int unused) { got_interrupted = 1;}... struct sigaction sa; sa.sa_handler = caught_signal; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; if (sigaction(SIGINT, &sa, NULL) == -1) ... handle error ... ...现在,在代码主体中,您可能希望“运行直到被中断”:
while (!got_interrupted) { ... do some work ... }这很好,直到您开始需要进行等待某些输入/输出的调用(例如
select或)为止
poll。“等待”操作需要等待该I / O,但 也
需要等待
SIGINT中断。如果您只写:
while (!got_interrupted) { ... do some work ... result = select(...); }那么可能会 在 您调用
select()或
之前
poll()而不是之后发生中断。在这种情况下,您确实被打断了,并且变量
got_interrupted被设置了,但是在那之后,您开始等待。您应该
got_interrupted在开始等待之前而不是之后检查变量。
您可以尝试编写:
while (!got_interrupted) { ... do some work ... if (!got_interrupted) result = select(...); }这会缩小“竞赛窗口”,因为现在您可以在“执行一些工作”代码时检测到中断(如果发生);但是仍然存在竞争,因为中断可能 在 您测试变量 后立即 发生,而
在 选择或轮询 之前 发生。
解决方案是使用
sigprocmask(或在POSIX线程代码中
pthread_sigmask)的信号阻止属性使“测试,然后等待”序列“原子”化:
sigset_t mask, omask;...while (!got_interrupted) { ... do some work ... sigemptyset(&mask); sigaddset(&mask, SIGINT); if (sigprocmask(SIG_BLOCK, &mask, &omask)) ... handle error ... if (got_interrupted) { sigprocmask(SIG_SETMASK, &omask, NULL); break; } result = pselect(..., &omask); sigprocmask(SIG_SETMASK, &omask, NULL); }(上面的代码实际上并不是那么好,它只是为了说明而不是为了效率而设计的-稍微不同地进行信号屏蔽操作,并以不同的方式放置“中断”测试会更加有效)。
但是,直到您真正开始需要捕获之前
SIGINT,您只需要比较
select()和
poll()(并且如果您开始需要大量的描述符,则某些基于事件的东西(例如,
epoll()其中之一)要比任何一个都更有效)。



