我有一个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
编写了一个可行的解决方案,但事实证明,由于兼容性问题,我不允许使用它。
提前感谢您的任何反馈。
答案 0 :(得分:2)
正如您所发现的那样,翻转旗帜还不够。我能想到几种方法。
不需要旗帜。
但是,正如您所指出的,pthread_cancel
不适用于您的环境,许多人认为它很容易被滥用。
read()
保持旗帜,尽量少耐心。
使用短暂超时替换read
select
或poll
可能已经足够了。你是否关心线程是否会立即退出"还是在几秒钟内?
close
或shutdown
套接字
没有标志,但信号处理程序只是close()
或shutdown
套接字,因此唤醒了read
。
在多线程环境中关闭fds会很危险,因为fd可以被另一个线程open
或pipe
或更多异域调用立即重用。例如,让信号处理程序接近fd 5,而另一个线程使用fd 5作为可读端,pipe
就在你的read
之前。正如@R ..提到的那样,shutdown
可能会或可能不会起作用,但是,如果确实如此,那么这里是安全的。
没有国旗。相反,您的信号处理程序将一个字节写入非阻塞管道,并且您的线程select
位于有趣的套接字和管道的可读端。如果数据到达(或已经在等待)后者,则线程知道它已经发出信号。
pselect
可以使用flag。
功能性,原子pselect
和周到的信号屏蔽将为您提供可靠的EINTR信号传递指示。警告:glibc中此调用的早期版本不是 atomic。
答案 1 :(得分:1)
我知道这个问题基本上有两个通用解决方案,没有使用线程取消:
反复发送信号,指数退避,这样就不会阻止目标线程进行调度,直到目标线程响应它收到信号为止。
使用调用longjmp
或siglongjmp
的信号处理程序,而不是中断无所事事信号处理程序。如果您中断异步信号不安全功能,则会导致未定义的行为,您需要使用pthread_sigmask
来阻止信号被阻止,除非它适合作用于信号。您跳转的jmp_buf
或sigjmp_buf
(或指向它的指针)应位于线程本地存储中,以便信号处理程序可以访问正确线程的那个。
此外,对于从套接字读取的特定情况,可能还有其他方法可行:
不是直接调用read
,而是先使用pselect
等待套接字变为可读,这可以通过等待原子解锁信号。不幸的是,这排除了使用其值超过FD_SETSIZE
的文件描述符。在Linux上,ppoll
函数避免了这个问题,但它是非标准的。一旦pselect
或ppoll
确定套接字可读,read
将不会阻止(除非另一个线程能够先窃取输入)。此方法的另一个变体是使用self-pipe trick和普通poll
(可移植)。
在套接字上调用shutdown
。您必须测试这是否可靠地使read
在您关注的系统上失败,因为我认为它没有明确指出,但这很可能是有效的,干净简单(假设您在取消操作后不再需要套接字)。