【问题标题】:Fixing race condition when sending signal to interrupt system call修复发送信号中断系统调用时的竞争条件
【发布时间】:2014-11-06 02:58:30
【问题描述】:

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

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 编写了一个可行的解决方案,但事实证明,由于兼容性问题,我不允许使用它。

提前感谢您的任何反馈。

【问题讨论】:

  • 你用的是哪个信号?
  • 带有 pthread_kill 的 SIGUSR1
  • 您可以使用 poll/select/epoll 指定阻塞超时。

标签: c multithreading signals interrupt


【解决方案1】:

正如您所发现的,仅翻转一面旗帜是不够的。我能想到几种方法。

取消线程

不需要标志。

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

限时拦截read()

保持旗帜,并保持最低限度的耐心。

read 替换为selectpoll 并缩短超时时间可能就足够了。你关心线程是“立即”退出还是在几秒钟内退出?

closeshutdown 来自信号处理程序的套接字

没有标志,而是一个简单的close()s 或shutdowns 套接字的信号处理程序,从而唤醒read

在多线程环境中关闭 fd 可能很危险,因为 fd 可以立即被另一个线程的 openpipe 或更多外来调用重新使用。例如,让信号处理程序关闭 fd 5 而另一个线程在 read 之前使用 fd 5 为可读端创建 pipe 会很糟糕。正如@R.. 所提到的,shutdown 可能有效,也可能无效,但如果有效,这里是安全的。

使用 self-pipe 技巧

没有标志。相反,您的信号处理程序将一个字节写入非阻塞管道,并且您的线程 selects 在有趣的套接字和管道的可读端上。如果后者有数据到达(或已经在等待),线程就知道它已经收到信号了。

使用pselect

可以使用标志。

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

【讨论】:

  • 来自信号处理程序的 close 在多线程程序中绝对是不安全。信号处理程序返回后,read 将读取一个不相关的文件,该文件恰好被分配了相同的描述符编号,因为它是最低可用的!不过shutdown 是安全的。
【解决方案2】:

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

  1. 使用指数退避重复发送信号,这样您就不会阻止目标线程被调度,直到目标线程响应它收到信号。

  2. 使用调用longjmpsiglongjmp 的信号处理程序,而不是中断无操作信号处理程序。由于如果您中断异步信号不安全函数会导致未定义的行为,您需要使用pthread_sigmask 来保持信号阻塞,除非在适合对信号采取行动的时候。您跳转的jmp_bufsigjmp_buf(或指向它的指针)应位于线程本地存储中,以便信号处理程序可以访问正确线程的那个。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-27
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-03
    相关资源
    最近更新 更多