如何唤醒在recvmsg()中睡觉的pthread?

时间:2015-03-24 16:07:49

标签: c linux sockets pthreads

我有一个使用pthreads的Unix守护进程。一个线程在循环中运行,使用recvmsg读取网络数据包。当守护进程收到一个信号时,会设置一个标志,告诉所有线程突破它们的循环并退出。但是,“侦听”线程永远不会检查该标志,直到下一个数据包到达并且recvmsg返回,这可能需要一些时间。

使用SIGINT向{listen}线程发送pthread_kill使其突破recvmsg,但它也会调用信号处理程序,该处理程序通过按Ctrl + C来处理发送的中断控制台。这是不需要的。强制recvmsg提前返回的另一种方法是关闭它正在侦听的套接字,但我认为必须有更好的方法。你知道它是什么吗?

此守护程序仅在Linux上使用,如果它有所不同。

4 个答案:

答案 0 :(得分:2)

三个想法:

想法#1: 为什么不是另一个信号信号SIGUSR1SIGUSR2仅用于用户定义的信令。如果您已经设置了信号处理程序并且通常对其使用感到满意,这可能是最简单的方法。

创意#2: 像其他人建议的那样发送虚拟数据包。

创意#3: 如果您真的想避免发送数据包,请尝试使用非阻塞I / O,以便recvmsg根本不会阻塞(请参阅MSG_DONTWAIT)。

相反,调用epoll | poll | select来阻止套接字recv事件和另一个文件描述符。

这样的循环看起来像这样:

while(1){
   recvmsg();
   do_stuff();
}

变为:

while(1){
   wait_for_events();
   if( /* FD used for cancellation is readable */ )
      break;
   else
      recvmsg();
      do_stuff();
}

其中wait_for_eventsselectpollepoll_wait。设置等待列表以包括套接字和其他fd,例如管道,unix域套接字或POSIX消息队列(Linux中的文件)。

要取消套接字读取器线程,请在取消文件对象上执行write。然后让读者检查从民意调查/ epoll / select中醒来时是否能够读取fd。

如果有多个事物处理recvmsg的实例,你应该能够使用一个这样的文件来唤醒所有线程,如果使用epoll,请确保使用等级触发而不是边缘触发

如果您不确定要使用哪三个来电,我建议您从poll开始,如果您已经非常了解,请仅使用selectepoll你知道你需要它的可扩展性。

答案 1 :(得分:1)

如果这是UDP,我使用的常用方法是设置退出条件标志,然后只发送一个1字节的数据包以强制recvfrom / recvmsg调用返回。

或者,您可以在套接字SO_RCVTIMEO上设置超时值(例如1秒)。当套接字等待超时时,您可以检查退出条件。如果未设置退出条件,则再次调用recvmsg。

https://stackoverflow.com/a/4182564/104458

答案 2 :(得分:1)

pthread_cancel可能是您正在寻找的内容。请仔细阅读手册页,不要过度使用。

答案 3 :(得分:1)

将空信号处理程序安装到未使用的POSIX实时信号SIGRTMIN+0SIGRTMAX-0,然后使用pthread_kill()将该信号发送到recvmsg()中阻止的线程。实时信号排队(可靠),而标准信号则不排队。这意味着如果您使用实时信号,同时中断两个不同的线程,但如果使用正常信号(例如SIGUSR1)可能会失败。

向信号处理程序传递信号,即使信号处理函数的主体为空,也会中断库函数或系统调用。 (当然,假设您使用sigaction()安装信号处理程序而没有SA_RESTART标志。请参阅man 7 signal"系统调用中断和库函数信号处理程序" 了解详情。)


这种方法可能遇到的一个问题(使用信号来中断阻塞I / O功能)是在目标线程实际阻塞I / O功能之前提前发出信号的时候。 那个窗口真的无法避免,你只需要处理它。

就个人而言,我喜欢使用专用线程来维护超时结构,比如

struct timeout {
    pthread_t        who;
    struct timespec  when;  /* Using CLOCK_MONOTONIC */
    volatile int     state;
};

每个线程都可以获取和释放 - 我发现线程可以同时保存多个超时特别有用 - 以及一个简单的原子函数来检查状态(用于例如循环条件) )。

诀窍是,当超时最初触发时,由于上述有问题的时间窗口,它不会被删除。相反,我喜欢标记超时"经过",然后在几毫秒后重新安装毫秒。重复此过程,直到目标线程释放超时。 (每次触发超时时,我都会向目标线程发送POSIX实时信号,并安装空信号处理函数。)

这样,即使您的recv() / send()循环偶尔会错过信号,也会很快收到通知。

如果你取消线程,你还需要安装一个线程清理处理程序来释放线程拥有的所有超时。否则你可能会泄漏"超时结构。

如果使用clock_gettime()pthread_cond_timedwait(),等待新的超时条目(由其他线程添加)或现有的超时时间过去,超时管理线程将非常轻量级并且不会消耗太多内存或CPU时间。请注意,如果使用通过CLOCK_MONOTONIC选择了该时钟的属性集创建添加了new-timeouts的条件变量,则可以使用pthread_condattr_setclock()