我建议先比较 select() 与 poll()。 Linux 还同时提供pselect() 和ppoll(); const sigset_t * 和 pselect() 和 ppoll() (与 select() 和 poll() 相比)的额外 const sigset_t * 参数对每个“p-variant”具有相同的效果。如果您不使用信号,则无需防范竞争,因此基本问题实际上是关于效率和编程的易用性。
同时这里已经有一个 stackoverflow.com 的答案:what are the differences between poll and select。
至于比赛:一旦开始使用信号(无论出于何种原因),您将了解到,一般来说,信号处理程序应该只设置一个 volatile sig_atomic_t 类型的变量来指示已检测到信号。其根本原因是许多库调用不是re-entrant,并且当您“处于”此类例程的“中间”时可以传递信号。例如,简单地将消息打印到 stdout (C) 或 cout (C++) 等流式数据结构可能会导致重入问题。
假设您的代码使用了volatile sig_atomic_t flag 变量,可能是为了捕获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(...); /* or result = poll(...) */
}
那么中断可能会发生在就在您调用select() 或poll() 之前,而不是之后。在这种情况下,你确实被打断了——变量got_interrupted被设置——但在那之后,你开始等待。您应该在开始等待之前检查got_interrupted 变量,而不是之后。
你可以试试写:
while (!got_interrupted) {
... do some work ...
if (!got_interrupted)
result = select(...); /* or result = poll(...) */
}
这会缩小“竞赛窗口”,因为现在您将检测到中断是否在您执行“做一些工作”代码时发生;但是仍然存在竞争,因为中断可能发生在就在您测试变量之后,但就在选择或轮询之前。
解决方案是使用sigprocmask(或者,在POSIX线程代码中,pthread_sigmask)的信号阻塞属性,使“测试,然后等待”序列“原子化”:
sigset_t mask, omask;
...
while (!got_interrupted) {
... do some work ...
/* begin critical section, test got_interrupted atomically */
sigemptyset(&mask);
sigaddset(&mask, SIGINT);
if (sigprocmask(SIG_BLOCK, &mask, &omask))
... handle error ...
if (got_interrupted) {
sigprocmask(SIG_SETMASK, &omask, NULL); /* restore old signal mask */
break;
}
result = pselect(..., &omask); /* or ppoll() etc */
sigprocmask(SIG_SETMASK, &omask, NULL);
/* end critical section */
}
(上面的代码实际上并没有那么好,它的结构是为了说明而不是效率——稍微不同地执行信号掩码操作,并以不同的方式放置“被中断”测试会更有效)。
在您真正开始需要捕获 SIGINT 之前,您只需比较 select() 和 poll()(如果您开始需要大量描述符,一些基于事件的东西,例如 epoll()比任何一个都更有效)。