简体   繁体   English

在发送信号以中断系统调用时解决竞争条件

[英]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. 我可以想到几种方法。

Cancel the thread 取消线程

No flag needed. 不需要标志。

However, pthread_cancel is not applicable to your environment, as you noted, and many consider it easy to misuse. 但是,正如您所指出的, pthread_cancel不适用于您的环境,许多人认为它很容易被滥用。

Time-limit the blocking 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 通过信号处理程序closeshutdown套接字

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可能会被另一个线程的openpipe或更多外来调用立即重新使用。 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可能会或可能不会起作用,但是,如果可行,在这里是安全的。

Use the self-pipe trick 使用自吸技巧

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. 如果数据到达(或已经在等待)后者,则线程知道已发出信号。

Use 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: 对于此类问题,我基本上有两种通用的解决方案,但不使用线程取消:

  1. 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. 重复发送具有指数补偿的信号,这样就不会阻止目标线程被调度,直到目标线程响应它已收到信号为止。

  2. Instead of an interrupting do-nothing signal handler, use a signal handler which calls longjmp or siglongjmp . 可以使用调用longjmpsiglongjmp的信号处理程序来代替中断的虚无信号处理程序。 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_bufsigjmp_buf (或指向它的指针)应位于线程本地存储中,以便信号处理程序可以访问正确的线程。

In addition, for your specific case of reading from a socket, there may be other approaches that work: 此外,对于您从套接字读取的特定情况,可能还有其他可行的方法:

  1. Instead of calling read directly, first wait for the socket to become readable using pselect , which can atomically unblock signals together with waiting. 与其直接调用readpselect直接使用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). 一旦pselectppoll确定套接字是可读的,则read不会阻塞(除非另一个线程能够先窃取输入)。 Another variant on this method is to use the self-pipe trick with plain poll (which is portable). 此方法的另一种变体是使用带有自动poll自管道技巧 (可移植)。

  2. 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM