如何唤醒正在 recvmsg() 中休眠的 pthread?
How to wake up a pthread which is sleeping in recvmsg()?
我有一个使用 pthreads 的小 Unix 守护进程。一个线程循环运行,使用 recvmsg
读取网络数据包。当守护进程收到信号时,会设置一个标志,告诉所有线程跳出循环并退出。但是,"listening" 线程从不检查标志,直到下一个数据包到达 recvmsg
returns,这可能需要一些时间。
使用 pthread_kill
将 SIGINT
传递给 "listening" 线程使其脱离 recvmsg
,但它也会调用信号处理程序处理通过按下发送的中断Ctrl+C 在控制台。那是不需要的。另一种提前将 recvmsg
强制为 return 的方法是关闭它正在侦听的套接字,但我认为必须有更好的方法。你知道它是什么吗?
此守护程序仅在 Linux 上使用,如果它有所不同的话。
如果这是 UDP,我使用的一种常用方法是设置退出条件标志,然后只发送一个 1 字节的数据包以强制 recvfrom/recvmsg 调用 return。
或者,您可以在套接字上设置超时值(例如 1 秒)SO_RCVTIMEO。当套接字等待超时时,您可以检查退出条件。如果没有设置退出条件,则再次调用recvmsg。
pthread_cancel
可能就是您要找的。不过请仔细阅读手册页,不要过度使用它。
三个想法:
想法 #1:
为什么不是另一个信号?信号 SIGUSR1
和 SIGUSR2
仅用于用户定义的信号。如果您已经设置了信号处理程序并且通常熟悉它们的使用,这可能是最简单的方法。
想法 #2:
按照其他人的建议发送一个虚拟数据包。
想法 #3:
如果你真的想避免发送数据包,请尝试使用非阻塞 I/O 这样 recvmsg 就不会阻塞(参见 MSG_DONTWAIT
)。
相反,调用 epoll | poll | select
来阻止套接字接收事件和另一个文件描述符。
所以循环看起来像这样:
while(1){
recvmsg();
do_stuff();
}
变为:
while(1){
wait_for_events();
if( /* FD used for cancellation is readable */ )
break;
else
recvmsg();
do_stuff();
}
其中 wait_for_events
是 select
、poll
或 epoll_wait
。设置等待列表以包括您的套接字和额外的 fd,例如管道、unix 域套接字或 POSIX 消息队列(这是 Linux 中的文件)。
要取消套接字 reader 线程,请对取消文件对象执行 write
。然后让 reader(s) 检查从 poll/epoll/select.
唤醒时是否能够读取该 fd
如果事物处理有多个实例,您应该能够使用一个这样的文件来唤醒所有线程 recvmsg
,如果使用 [=22],请确保使用级别触发而不是边缘触发=].
如果您不确定使用这三个调用中的哪一个,我建议从 poll
开始,如果您已经非常了解它,则只使用 select
或如果你知道你需要它的可扩展性。
将空信号处理程序安装到未使用的 POSIX 实时信号,SIGRTMIN+0
到 SIGRTMAX-0
,并使用 pthread_kill()
将该信号发送到阻塞在 recvmsg()
。实时信号是排队的(可靠的),而标准信号不是。这意味着如果您使用实时信号,同时中断两个不同的线程是可行的,但如果使用普通信号(例如 SIGUSR1
)可能会失败。
将信号传递给信号处理程序会中断库函数或系统调用——即使信号处理函数的主体为空。 (当然,假设您使用 sigaction()
without the SA_RESTART
flag. See man 7 signal
安装信号处理程序,详情请参见 "Interruption of system calls and library functions by signal handlers"。)
您可能会遇到这种方法(使用信号中断阻塞 I/O 函数)的一个问题是,在目标线程实际阻塞 [=52= 之前提早发出信号时] 功能。
那window真的无法避免,你只能去面对它。
就个人而言,我喜欢使用专用线程来维护超时结构,例如
struct timeout {
pthread_t who;
struct timespec when; /* Using CLOCK_MONOTONIC */
volatile int state;
};
每个线程都可以获取和释放——我发现一个线程可以同时保持多个超时特别有用——还有一个简单的原子函数来检查状态(例如在循环条件中使用) ).
诀窍是当超时最初触发时,由于上述时间问题 window,它不会被删除。相反,我喜欢标记超时 "elapsed",并在几毫秒后重新设置它。如此重复,直到目标线程释放超时。 (每次触发超时,我都会向目标线程发送一个 POSIX 实时信号,并安装一个空信号处理函数。)
这样,即使您的 recv()
/send()
循环偶尔错过信号,它们也会很快收到通知。
如果您曾经取消线程,您还需要安装一个线程清理处理程序来释放线程拥有的所有超时。否则你可能 "leak" 超时结构。
如果您使用clock_gettime()
and pthread_cond_timedwait()
,等待新的超时条目(由其他线程添加)或现有超时结束,超时管理线程将非常轻量级并且不会消耗太多内存或CPU 时间。请注意,如果您使用通过 pthread_condattr_setclock()
选择时钟的属性集创建新添加的超时条件变量,则可以使用 CLOCK_MONOTONIC
。
我有一个使用 pthreads 的小 Unix 守护进程。一个线程循环运行,使用 recvmsg
读取网络数据包。当守护进程收到信号时,会设置一个标志,告诉所有线程跳出循环并退出。但是,"listening" 线程从不检查标志,直到下一个数据包到达 recvmsg
returns,这可能需要一些时间。
使用 pthread_kill
将 SIGINT
传递给 "listening" 线程使其脱离 recvmsg
,但它也会调用信号处理程序处理通过按下发送的中断Ctrl+C 在控制台。那是不需要的。另一种提前将 recvmsg
强制为 return 的方法是关闭它正在侦听的套接字,但我认为必须有更好的方法。你知道它是什么吗?
此守护程序仅在 Linux 上使用,如果它有所不同的话。
如果这是 UDP,我使用的一种常用方法是设置退出条件标志,然后只发送一个 1 字节的数据包以强制 recvfrom/recvmsg 调用 return。
或者,您可以在套接字上设置超时值(例如 1 秒)SO_RCVTIMEO。当套接字等待超时时,您可以检查退出条件。如果没有设置退出条件,则再次调用recvmsg。
pthread_cancel
可能就是您要找的。不过请仔细阅读手册页,不要过度使用它。
三个想法:
想法 #1:
为什么不是另一个信号?信号 SIGUSR1
和 SIGUSR2
仅用于用户定义的信号。如果您已经设置了信号处理程序并且通常熟悉它们的使用,这可能是最简单的方法。
想法 #2: 按照其他人的建议发送一个虚拟数据包。
想法 #3:
如果你真的想避免发送数据包,请尝试使用非阻塞 I/O 这样 recvmsg 就不会阻塞(参见 MSG_DONTWAIT
)。
相反,调用 epoll | poll | select
来阻止套接字接收事件和另一个文件描述符。
所以循环看起来像这样:
while(1){
recvmsg();
do_stuff();
}
变为:
while(1){
wait_for_events();
if( /* FD used for cancellation is readable */ )
break;
else
recvmsg();
do_stuff();
}
其中 wait_for_events
是 select
、poll
或 epoll_wait
。设置等待列表以包括您的套接字和额外的 fd,例如管道、unix 域套接字或 POSIX 消息队列(这是 Linux 中的文件)。
要取消套接字 reader 线程,请对取消文件对象执行 write
。然后让 reader(s) 检查从 poll/epoll/select.
如果事物处理有多个实例,您应该能够使用一个这样的文件来唤醒所有线程 recvmsg
,如果使用 [=22],请确保使用级别触发而不是边缘触发=].
如果您不确定使用这三个调用中的哪一个,我建议从 poll
开始,如果您已经非常了解它,则只使用 select
或如果你知道你需要它的可扩展性。
将空信号处理程序安装到未使用的 POSIX 实时信号,SIGRTMIN+0
到 SIGRTMAX-0
,并使用 pthread_kill()
将该信号发送到阻塞在 recvmsg()
。实时信号是排队的(可靠的),而标准信号不是。这意味着如果您使用实时信号,同时中断两个不同的线程是可行的,但如果使用普通信号(例如 SIGUSR1
)可能会失败。
将信号传递给信号处理程序会中断库函数或系统调用——即使信号处理函数的主体为空。 (当然,假设您使用 sigaction()
without the SA_RESTART
flag. See man 7 signal
安装信号处理程序,详情请参见 "Interruption of system calls and library functions by signal handlers"。)
您可能会遇到这种方法(使用信号中断阻塞 I/O 函数)的一个问题是,在目标线程实际阻塞 [=52= 之前提早发出信号时] 功能。 那window真的无法避免,你只能去面对它。
就个人而言,我喜欢使用专用线程来维护超时结构,例如
struct timeout {
pthread_t who;
struct timespec when; /* Using CLOCK_MONOTONIC */
volatile int state;
};
每个线程都可以获取和释放——我发现一个线程可以同时保持多个超时特别有用——还有一个简单的原子函数来检查状态(例如在循环条件中使用) ).
诀窍是当超时最初触发时,由于上述时间问题 window,它不会被删除。相反,我喜欢标记超时 "elapsed",并在几毫秒后重新设置它。如此重复,直到目标线程释放超时。 (每次触发超时,我都会向目标线程发送一个 POSIX 实时信号,并安装一个空信号处理函数。)
这样,即使您的 recv()
/send()
循环偶尔错过信号,它们也会很快收到通知。
如果您曾经取消线程,您还需要安装一个线程清理处理程序来释放线程拥有的所有超时。否则你可能 "leak" 超时结构。
如果您使用clock_gettime()
and pthread_cond_timedwait()
,等待新的超时条目(由其他线程添加)或现有超时结束,超时管理线程将非常轻量级并且不会消耗太多内存或CPU 时间。请注意,如果您使用通过 pthread_condattr_setclock()
选择时钟的属性集创建新添加的超时条件变量,则可以使用 CLOCK_MONOTONIC
。