[英]Fixing race condition when sending signal to interrupt system call
I have a thread that read()
s from a socket, and I want to be able to stop the thread asynchronously. 我有一个从套接字
read()
的线程,并且我希望能够异步停止该线程。 The thread pseudocode looks like: 线程伪代码如下所示:
int needs_quit = 0;
void *thread_read(void *arg)
{
while(1)
{
if(needs_quit)
{
close(sock_fd);
return NULL;
}
int ret = read(sock_fd ...);
if(ret == EINTR) //we received an interrupt signal to stop
{
close(sock_fd);
return NULL;
}
//do stuff with read data
}
}
The signal handler simply sets needs_quit
to 1. Most of the time, this code will work. 信号处理程序仅将
needs_quit
设置为1。在大多数情况下,此代码将起作用。 But, if a signal arrives after if(needs_quit)
and before read(sock_fd ...)
, then read()
will not be interrupted, and the thread will never stop. 但是,如果信号在
if(needs_quit)
和read(sock_fd ...)
之前到达,则read()
将不会被中断,并且线程将永远不会停止。 What is the best solution for this problem? 解决此问题的最佳方法是什么?
I should point out that I managed to write a working solution with pthread_cancel
, but as it turns out, I'm not allowed to use that due to compatibility issues. 我应该指出,我设法用
pthread_cancel
编写了一个可行的解决方案,但事实证明,由于兼容性问题,我不允许使用该解决方案。
Thanks in advance for any feedback. 预先感谢您的任何反馈。
As you discovered, flipping a flag isn't enough. 正如您所发现的,仅仅翻转一个标志是不够的。 There are several approaches I can think of.
我可以想到几种方法。
No flag needed. 不需要标志。
However, pthread_cancel
is not applicable to your environment, as you noted, and many consider it easy to misuse. 但是,正如您所指出的,
pthread_cancel
不适用于您的环境,许多人认为它很容易被滥用。
read()
read()
Keep the flag, and be minimally patient. 保持旗帜,并保持最小耐心。
Replacing the read
with a select
or poll
with a short timeout may be good enough. 用
select
或短时poll
代替read
可能就足够了。 Do you care if the thread quits "instantly" or within a few seconds? 您是否在乎线程是否“立即”退出或在几秒钟内退出?
close
or shutdown
the socket from a signal handler close
或shutdown
套接字 No flag, but a signal handler that simply close()
s or shutdown
s the socket and thus wakes up the read
. 没有标志,只是一个信号处理程序,它仅
close()
套接字或shutdown
套接字,从而唤醒了read
。
Closing fds can be dangerous in a multithreaded environment, as the fd can be immediately re-used by another thread's open
or pipe
or more exotic calls. 在多线程环境中,关闭fds可能很危险,因为fd可能会被另一个线程的
open
或pipe
或更多外来调用立即重新使用。 It'd be bad, for instance, to have the signal handler close fd 5 while another thread makes a pipe
using fd 5 for the readable end, just prior to your read
. 它会是坏的,例如,有接近FD 5中的信号处理程序,而另一个线程进行
pipe
采用FD 5读取结尾,只是你之前read
。 As @R.. mentions, shutdown
may or may not work, but, if it does, is safe here. 正如@R ..提到的那样,
shutdown
可能会或可能不会起作用,但是,如果可行,在这里是安全的。
No flag. 没有标志。 Instead your signal handler writes a byte to a non-blocking pipe, and your thread
select
s on both the interesting socket and the readable end of the pipe. 相反,您的信号处理程序将字节写入非阻塞管道,并且线程在有趣的套接字和管道的可读端都
select
s。 If data arrives (or is already waiting) on the latter, the thread knows it has been signalled. 如果数据到达(或已经在等待)后者,则线程知道已发出信号。
pselect
pselect
Can use flag. 可以使用标志。
A functional, atomic pselect
and thoughtful signal masking will give you reliable EINTR indications of signal delivery. 功能性,原子性的
pselect
和周到的信号屏蔽将为您提供可靠的EINTR信号传递指示。 Warning: early versions of this call in glibc were not atomic. 警告:glibc中此调用的早期版本不是原子的。
There are basically two general solutions I know for this sort of problem, short of using thread cancellation: 对于此类问题,我基本上有两种通用的解决方案,但不使用线程取消:
Repeatedly send the signal, with exponential backoff so that you don't keep the target thread from getting scheduled, until the target thread responds that it got the signal. 重复发送具有指数补偿的信号,这样就不会阻止目标线程被调度,直到目标线程响应它已收到信号为止。
Instead of an interrupting do-nothing signal handler, use a signal handler which calls longjmp
or siglongjmp
. 可以使用调用
longjmp
或siglongjmp
的信号处理程序来代替中断的虚无信号处理程序。 Since this results in undefined behavior if you interrupt an async-signal-unsafe function, you need to use pthread_sigmask
to keep the signal blocked except at times when it's appropriate to act on the signal. 由于如果您中断异步信号不安全函数会导致不确定的行为,因此需要使用
pthread_sigmask
来阻止信号,除非在适当的时候对信号进行操作。 The jmp_buf
or sigjmp_buf
that you jump (or a pointer to it) should lie in thread-local storage so that the signal handler has access to the one for the correct thread. 跳转的
jmp_buf
或sigjmp_buf
(或指向它的指针)应位于线程本地存储中,以便信号处理程序可以访问正确的线程。
In addition, for your specific case of reading from a socket, there may be other approaches that work: 此外,对于您从套接字读取的特定情况,可能还有其他可行的方法:
Instead of calling read
directly, first wait for the socket to become readable using pselect
, which can atomically unblock signals together with waiting. 与其直接调用
read
, pselect
直接使用pselect
等待套接字变得可读,这可以在等待时自动取消阻塞信号。 Unfortunately, this precludes using file descriptors whose values exceed FD_SETSIZE
. 不幸的是,这排除了使用值超过
FD_SETSIZE
文件描述符。 On Linux, the ppoll
function avoids this problem, but it's non-standard. 在Linux上,
ppoll
函数可避免此问题,但这是非标准的。 Once pselect
or ppoll
has determined that the socket is readable, read
will not block (unless another thread is able to steal the input first). 一旦
pselect
或ppoll
确定套接字是可读的,则read
不会阻塞(除非另一个线程能够先窃取输入)。 Another variant on this method is to use the self-pipe trick with plain poll
(which is portable). 此方法的另一种变体是使用带有自动
poll
的自管道技巧 (可移植)。
Call shutdown
on the socket. 在套接字上调用
shutdown
。 You would have to test whether this reliably makes the read
fail on systems you care about, since I don't think it's specified clearly, but this is very likely to work and clean and simple (assuming you don't need the socket anymore after cancelling the operation). 您将必须测试这是否确实使您关心的系统上的
read
失败,因为我认为没有明确指定,但这很可能有效且干净,简单(假设以后您不再需要套接字了)取消操作)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.