发送信号到中断系统调用时修复竞争条件

时间:2014-09-12 02:07:37

标签: c multithreading signals interrupt

我有一个read()来自套接字的线程,我希望能够异步停止该线程。线程伪代码如下:

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
    }
}

信号处理程序只需将needs_quit设置为1.大多数情况下,此代码都可以使用。但是,如果信号在if(needs_quit)之后和read(sock_fd ...)之前到达,那么read()将不会被中断,线程将永远不会停止。这个问题的最佳解决方案是什么?

我应该指出,我设法用pthread_cancel编写了一个可行的解决方案,但事实证明,由于兼容性问题,我不允许使用它。

提前感谢您的任何反馈。

2 个答案:

答案 0 :(得分:2)

正如您所发现的那样,翻转旗帜还不够。我能想到几种方法。

取消主题

不需要旗帜。

但是,正如您所指出的,pthread_cancel不适用于您的环境,许多人认为它很容易被滥用。

时间限制阻止read()

保持旗帜,尽量少耐心。

使用短暂超时替换read selectpoll可能已经足够了。你是否关心线程是否会立即退出"还是在几秒钟内?

来自信号处理程序的

closeshutdown套接字

没有标志,但信号处理程序只是close()shutdown套接字,因此唤醒了read

在多线程环境中关闭fds会很危险,因为fd可以被另一个线程openpipe或更多异域调用立即重用。例如,让信号处理程序接近fd 5,而另一个线程使用fd 5作为可读端,pipe就在你的read之前。正如@R ..提到的那样,shutdown可能会或可能不会起作用,但是,如果确实如此,那么这里是安全的。

使用自管技巧

没有国旗。相反,您的信号处理程序将一个字节写入非阻塞管道,并且您的线程select位于有趣的套接字和管道的可读端。如果数据到达(或已经在等待)后者,则线程知道它已经发出信号。

使用pselect

可以使用flag。

功能性,原子pselect和周到的信号屏蔽将为您提供可靠的EINTR信号传递指示。警告:glibc中此调用的早期版本不是 atomic。

答案 1 :(得分:1)

我知道这个问题基本上有两个通用解决方案,没有使用线程取消:

  1. 反复发送信号,指数退避,这样就不会阻止目标线程进行调度,直到目标线程响应它收到信号为止。

  2. 使用调用longjmpsiglongjmp的信号处理程序,而不是中断无所事事信号处理程序。如果您中断异步信号不安全功能,则会导致未定义的行为,您需要使用pthread_sigmask来阻止信号被阻止,除非它适合作用于信号。您跳转的jmp_bufsigjmp_buf(或指向它的指针)应位于线程本地存储中,以便信号处理程序可以访问正确线程的那个。

  3. 此外,对于从套接字读取的特定情况,可能还有其他方法可行:

    1. 不是直接调用read,而是先使用pselect等待套接字变为可读,这可以通过等待原子解锁信号。不幸的是,这排除了使用其值超过FD_SETSIZE的文件描述符。在Linux上,ppoll函数避免了这个问题,但它是非标准的。一旦pselectppoll确定套接字可读,read将不会阻止(除非另一个线程能够先窃取输入)。此方法的另一个变体是使用self-pipe trick和普通poll(可移植)。

    2. 在套接字上调用shutdown。您必须测试这是否可靠地使read在您关注的系统上失败,因为我认为它没有明确指出,但这很可能是有效的,干净简单(假设您在取消操作后不再需要套接字)。