I have a thread that read()
s from a socket, and I want to be able to stop the thread asynchronously. The thread pseudocode looks like:
我有一个线程从套接字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
}
}
The signal handler simply sets needs_quit
to 1. Most of the time, this code will work. 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. What is the best solution for this problem?
信号处理程序只需将needs_quit设置为1.大多数情况下,此代码将起作用。但是,如果信号在if(needs_quit)之后和读取之前(sock_fd ...)到达,那么read()将不会被中断,并且线程将永远不会停止。这个问题的最佳解决方案是什么?
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.
提前感谢您的任何反馈。
2 个解决方案
#1
2
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()
Keep the flag, and be minimally patient.
保持旗帜,并保持最低限度的耐心。
Replacing the read
with a select
or poll
with a short timeout may be good enough. Do you care if the thread quits "instantly" or within a few seconds?
使用短暂超时的select或poll替换读取可能已经足够了。如果线程“立即”退出或在几秒钟内退出,您是否在乎?
close
or shutdown
the socket from a signal handler
No flag, but a signal handler that simply close()
s or shutdown
s the socket and thus wakes up the 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. 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
. As @R.. mentions, shutdown
may or may not work, but, if it does, is safe here.
在多线程环境中关闭fds可能很危险,因为fd可以被另一个线程的open或pipe或更奇特的调用立即重用。例如,让信号处理程序关闭fd 5而另一个线程在读取之前使用fd 5作为可读端的管道是很糟糕的。正如@R ..提到的那样,关闭可能会或可能不会起作用,但是,如果确实如此,那么这里是安全的。
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. If data arrives (or is already waiting) on the latter, the thread knows it has been signalled.
没有国旗。相反,您的信号处理程序将一个字节写入非阻塞管道,并且您的线程选择有趣的套接字和管道的可读端。如果数据到达(或已经在等待)后者,则线程知道它已被发信号通知。
Use pselect
Can use flag.
可以用旗帜。
A functional, atomic pselect
and thoughtful signal masking will give you reliable EINTR indications of signal delivery. Warning: early versions of this call in glibc were not atomic.
功能性,原子性和周到信号屏蔽将为您提供可靠的EINTR信号传递指示。警告:glibc中此调用的早期版本不是原子的。
#2
1
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
orsiglongjmp
. Since this results in undefined behavior if you interrupt an async-signal-unsafe function, you need to usepthread_sigmask
to keep the signal blocked except at times when it's appropriate to act on the signal. Thejmp_buf
orsigjmp_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.使用调用longjmp或siglongjmp的信号处理程序而不是中断do-nothing信号处理程序。如果您中断异步信号不安全功能,则会导致未定义的行为,您需要使用pthread_sigmask来阻止信号被阻止,除非在适当的时候对信号起作用。您跳转的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 usingpselect
, which can atomically unblock signals together with waiting. Unfortunately, this precludes using file descriptors whose values exceedFD_SETSIZE
. On Linux, theppoll
function avoids this problem, but it's non-standard. Oncepselect
orppoll
has determined that the socket is readable,read
will not block (unless another thread is able to steal the input first). Another variant on this method is to use the self-pipe trick with plainpoll
(which is portable).不要直接调用read,而是首先使用pselect等待套接字可读,这可以通过等待原子解锁信号。不幸的是,这排除了使用其值超过FD_SETSIZE的文件描述符。在Linux上,ppoll函数避免了这个问题,但它是非标准的。一旦pselect或ppoll确定套接字是可读的,read就不会阻塞(除非另一个线程能够先窃取输入)。此方法的另一个变体是使用自管道技巧和普通轮询(可移植)。
-
Call
shutdown
on the socket. You would have to test whether this reliably makes theread
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).在套接字上调用shutdown。您必须测试这是否可靠地使您所关注的系统上的读取失败,因为我认为它没有明确指定,但这很可能工作,干净简单(假设您之后不再需要套接字)取消操作)。
#1
2
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()
Keep the flag, and be minimally patient.
保持旗帜,并保持最低限度的耐心。
Replacing the read
with a select
or poll
with a short timeout may be good enough. Do you care if the thread quits "instantly" or within a few seconds?
使用短暂超时的select或poll替换读取可能已经足够了。如果线程“立即”退出或在几秒钟内退出,您是否在乎?
close
or shutdown
the socket from a signal handler
No flag, but a signal handler that simply close()
s or shutdown
s the socket and thus wakes up the 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. 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
. As @R.. mentions, shutdown
may or may not work, but, if it does, is safe here.
在多线程环境中关闭fds可能很危险,因为fd可以被另一个线程的open或pipe或更奇特的调用立即重用。例如,让信号处理程序关闭fd 5而另一个线程在读取之前使用fd 5作为可读端的管道是很糟糕的。正如@R ..提到的那样,关闭可能会或可能不会起作用,但是,如果确实如此,那么这里是安全的。
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. If data arrives (or is already waiting) on the latter, the thread knows it has been signalled.
没有国旗。相反,您的信号处理程序将一个字节写入非阻塞管道,并且您的线程选择有趣的套接字和管道的可读端。如果数据到达(或已经在等待)后者,则线程知道它已被发信号通知。
Use pselect
Can use flag.
可以用旗帜。
A functional, atomic pselect
and thoughtful signal masking will give you reliable EINTR indications of signal delivery. Warning: early versions of this call in glibc were not atomic.
功能性,原子性和周到信号屏蔽将为您提供可靠的EINTR信号传递指示。警告:glibc中此调用的早期版本不是原子的。
#2
1
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
orsiglongjmp
. Since this results in undefined behavior if you interrupt an async-signal-unsafe function, you need to usepthread_sigmask
to keep the signal blocked except at times when it's appropriate to act on the signal. Thejmp_buf
orsigjmp_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.使用调用longjmp或siglongjmp的信号处理程序而不是中断do-nothing信号处理程序。如果您中断异步信号不安全功能,则会导致未定义的行为,您需要使用pthread_sigmask来阻止信号被阻止,除非在适当的时候对信号起作用。您跳转的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 usingpselect
, which can atomically unblock signals together with waiting. Unfortunately, this precludes using file descriptors whose values exceedFD_SETSIZE
. On Linux, theppoll
function avoids this problem, but it's non-standard. Oncepselect
orppoll
has determined that the socket is readable,read
will not block (unless another thread is able to steal the input first). Another variant on this method is to use the self-pipe trick with plainpoll
(which is portable).不要直接调用read,而是首先使用pselect等待套接字可读,这可以通过等待原子解锁信号。不幸的是,这排除了使用其值超过FD_SETSIZE的文件描述符。在Linux上,ppoll函数避免了这个问题,但它是非标准的。一旦pselect或ppoll确定套接字是可读的,read就不会阻塞(除非另一个线程能够先窃取输入)。此方法的另一个变体是使用自管道技巧和普通轮询(可移植)。
-
Call
shutdown
on the socket. You would have to test whether this reliably makes theread
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).在套接字上调用shutdown。您必须测试这是否可靠地使您所关注的系统上的读取失败,因为我认为它没有明确指定,但这很可能工作,干净简单(假设您之后不再需要套接字)取消操作)。