如何中止对 sigwaitinfo 的调用?
Posted
技术标签:
【中文标题】如何中止对 sigwaitinfo 的调用?【英文标题】:How can I abort the call to sigwaitinfo? 【发布时间】:2018-08-04 16:55:26 【问题描述】:背景
我的目标是在专用线程上处理某些信号,而不是让它们在发出信号时恰好在我的进程中运行的任何线程上处理。
我这样做如下(在本例中,仅适用于信号 16):
在主线程上,在任何其他线程启动之前(省略错误处理)
sigset_t sigset;
sigaddset(&sigset, 16);
sigprocmask(SIG_BLOCK, &sigset, nullptr);
然后我创建一个等待这些信号的线程(本例中只有 16 个):
std::thread _thread = std::thread([&]()
int ret = sigwaitinfo(&sigset, nullptr);
if (ret == 16)
// handle signal 16
);
这很好用。
问题
但是,我希望能够在需要时取消对 sigwaitinfo 的调用。
两个不充分的解决方案
我尝试了两种解决方案,但都不够用:
1。轮询
一个选项(有效)不是使用 sigwaitinfo,而是使用 sigtimedwait 接受超时参数。 这允许我使用轮询并在下次调用返回并设置一些取消标志时取消。 线程中的代码如下所示:
std::atomic<bool> _cancel (false);
std::thread _thread = std::thread([&]()
timespec _timespec 0, 1; // 1 second
int ret = sigtimedwait(&sigset, nullptr, _timespec);
if (_cancel)
return;
if (ret == 16)
// handle signal 16
);
为了取消,我只需要在主线程中设置_cancel标志。 此解决方案的问题在于,轮询会在(取消的)响应性和检查取消标志的繁忙工作量之间进行典型的权衡。
2。 raise()/sigqueue()/kill()
在这个解决方案中,我在信号掩码中添加了一个专用信号,例如 SIGUSR1,调用如下:
sigset_t sigset;
sigaddset(&sigset, 16);
sigaddset(&sigset, SIGUSR1); // <-- added call here
sigprocmask(SIG_BLOCK, &sigset, nullptr);
然后当我需要取消对 sigwaitinfo 的调用时,我设置一个取消标志并调用 raise(SIGUSR1)
线程中的代码如下所示:
std::atomic<bool> _cancel (false);
std::thread _thread = std::thread([&]()
int ret = sigwaitinfo(&sigset, nullptr);
if (_cancel) // <-- now check _cancel flag before handling signal
return;
if (ret == 16)
// handle signal 16
);
现在取消操作如下:
_cancel = true; // <-- set the flag before raising the signal
raise(SIGUSR1);
这个解决方案的问题是它不起作用,因为对 raise() 的调用不会导致 sigwaitinfo 在专用线程中返回。我相信根据文档,它只会在执行线程本身中引发信号。 sigqueue() 和 kill() 也不起作用。
总结
有没有办法让 sigwaitinfo 提前返回,而不需要一个循环来调用 sigtimedwait 并超时?
【问题讨论】:
为什么您认为raise()
的问题也会影响kill()
或sigqueue()
?
我确实尝试了 sigqueue 并且发生了同样的问题。我承认我没有尝试杀死,但是 raise 的文档说它相当于调用 kill(getpid(), sig)。我错了吗?
在多线程进程中,是的。如您所见,目标是整个进程,因此如果该进程中只有一个线程解除了信号阻塞,那么该线程应该接收信号,无论它是否是发送者。但是,sigqeue()
的应该也是如此,因此您的程序中可能存在另一个错误。例如:你还记得sigwaitinfo()
修改了sigset吗?
我会再试一次。但我想强调的是,当我测试这个时,我只有一个线程(专用轮询线程除外),此外,即使会有更多线程,它们也只会在调用 sigprocmask() 之后创建,所以我相信我假设阻塞的信号将在所有线程中被阻塞是正确的。 sigwaitinfo() 如何修改sigset?我没发现...
人 sigwaitinfo:sigwaitinfo() removes the signal from the set of pending signals and returns the signal number as its function result. If the info argument is not NULL, then the buffer that it points to is used to return a structure of type sig‐ info_t (see sigaction(2)) containing information about the signal.
【参考方案1】:
使用pthread_kill
向特定线程发送信号。
例如,代替raise(SIGUSR1);
做:
if(int rc = ::pthread_kill(_thread.native_handle(), SIGUSR1))
// Handle pthread_kill error.
【讨论】:
我一直在寻找一种更合作的方式来实现这一目标。如果可以避免的话,我不认为杀死一个线程是可取的。 @DavidSacksteinpthread_kill
向线程发送信号。您可能会将其与 pthread_cancel
混淆。
你是对的。我确实弄错了。但是,在我的系统中,我没有调用 raise() 的权限,所以我怀疑 pthread_kill 也会被阻止。我认为我找到的解决方案更简洁,因为它使用不同的取消渠道。
@DavidSackstein 是的,事件循环是最佳实践。【参考方案2】:
这是我找到的解决方案。
-
不要使用 sigtimedwait 等待,而是使用 signalfd 获取代表要处理的信号的文件描述符。 (需要首先调用 sigprocmask 或类似的方法,就像问题中提出的解决方案一样)。
调用 eventfd 以返回“事件”文件描述符。
在两个文件描述符上调用轮询等待。这块。循环执行。
通过在不同线程上写入事件文件描述符来取消信号。
当 poll 返回时,通过检查 revents 字段来检查哪个文件描述符发出信号。
如果事件文件描述符被通知从循环中中断。
否则(signalfd 描述符已发出信号)读取信号描述并通过调用处理程序来处理信号。然后再次循环调用 poll。
我已验证此解决方案是可靠的。
更多详细信息可以在以下文档中找到:
signalfd
,eventfd
和 poll
【讨论】:
以上是关于如何中止对 sigwaitinfo 的调用?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Swig 中止调用 C 函数的 Python 脚本?