【问题标题】:Cancelling thread that is stuck on epoll_wait取消卡在 epoll_wait 上的线程
【发布时间】:2013-09-07 23:31:05
【问题描述】:

我正在使用 C++ 和 pthread 进行一些事件处理。我有一个从我定义的事件队列中读取的主线程,以及一个填充事件队列的工作线程。队列当然是线程安全的。

工作线程有一个文件描述符列表,并创建一个 epoll 系统调用来获取这些文件描述符上的事件。它使用 epoll_wait 来等待 fd 上的事件。

现在的问题。假设我想干净地终止我的应用程序,如何正确取消工作线程? epoll_wait 不是pthread(7) 的取消点之一,因此它无法对pthread_cancel 做出正确反应。

工作线程 main() 长这样

while(m_WorkerRunning) {
    epoll_wait(m_EpollDescriptor, events, MAXEVENTS, -1);
    //handle events and insert to queue
}

m_WorkerRunning 在线程启动时设置为true,看起来我可以通过将主线程的m_WorkerRunning 设置为 false 来中断线程。问题是epoll_wait理论上可以永远等待。

我想到的其他解决方案是:我可以等待例如 X 个时隙,而不是永远等待 (-1),然后正确处理无事件情况,如果 m_WorkerRunning == false 然后退出循环并干净地终止工作线程。然后主线程将m_WorkerRunning 设置为false,并休眠X。但是我不确定这种epoll_wait 的性能,也不确定正确的X 是什么? 500毫秒? 1秒? 10 秒?

我想听听一些有经验的建议!

更多相关信息:我正在等待事件的 fd 是 /dev/input 中的设备,所以从技术上讲,我正在做某种输入子系统。目标操作系统是基于 ARM 架构的 Linux(最新内核)。

谢谢!

【问题讨论】:

  • 它正在等待事件,对吗?所以,发出信号!设置一些原子的“停止”布尔值,在每次 epoll_wait 之后检查,然后发出信号——添加一个你可以发出信号的 fd。
  • @MartinJames 我不确定我是否理解你。你建议添加一些我可以从主线程推送事件的虚拟 fd 吗?然后检查事件是否来自那个虚拟 fd,终止线程?
  • 我找到了这个man7.org/linux/man-pages/man2/eventfd.2.html 看起来可以用作虚拟fd。

标签: c++ pthreads epoll


【解决方案1】:

alk 上面的回答几乎是正确的。然而,这种差异是非常危险的。

如果你要发送一个信号来唤醒epoll_wait永远不要使用epoll_wait。您必须使用epoll_pwait,否则您可能会遇到一场比赛,而您的 epoll 永远不会醒来。

信号异步到达。如果您的SIGUSR1 在您检查关闭程序之后到达,但在您的循环返回到epoll_wait 之前,则信号不会中断等待(因为没有等待),但程序也不会退出。

这可能非常有可能或极不可能,具体取决于循环花费的时间与等待所花费的时间相关,但无论哪种方式,它都是一个错误。

alk 回答的另一个问题是它没有检查为什么等待被中断。可能有多种原因,其中一些与您的退出无关。

有关详细信息,请参阅pselect 的手册页。 epoll_pwait 的工作方式类似。

另外,切勿使用kill 向线程发送信号。请改用pthread_killkill 发送信号时的行为充其量是未定义的。无法保证正确的线程会接收到它,这可能会导致不相关的系统调用被中断,或者根本没有发生任何事情。

【讨论】:

    【解决方案2】:

    您可以向线程发送一个信号,该信号将中断对epoll_wait() 的阻塞调用。如果这样做,请像这样修改您的代码:

    while(m_WorkerRunning) 
    {
      int result = epoll_wait(m_EpollDescriptor, events, MAXEVENTS, -1);
      if (-1 == result)
      {
        if (EINTR == errno)
        {
          /* Handle shutdown request here. */ 
          break;
        }
        else
        {
          /* Error handling goes here. */
        }
      }
    
      /* Handle events and insert to queue. */
    }
    

    一种添加信号处理程序的方法:

    #include <signal.h>
    
    /* A generic signal handler doing nothing */
    void signal_handler(int sig)
    {
      sig = sig; /* Cheat compiler to not give a warning about an unused variable. */
    }
    
    /* Wrapper to set a signal handler */
    int signal_handler_set(int sig, void (*sa_handler)(int))
    {
      struct sigaction sa = {0};
      sa.sa_handler = sa_handler;
      return sigaction(sig, &sa, NULL);
    }
    

    为信号SIGUSR1设置这个处理程序:

    if (-1 == signal_handler_set(SIGUSR1, signal_handler))
    {
      perror("signal_handler_set() failed");
    }
    

    从另一个进程发送信号SIGUSR1

    if (-1 == kill(<target process' pid>, SIGUSR1))
    {
      perror("kill() failed");
    }
    

    让进程向自己发送信号:

    if (-1 == raise(SIGUSR1))
    {
      perror("raise() failed");
    }
    

    【讨论】:

    • 您能好心一点并添加一个如何发送此信号的示例吗?提前致谢! :)
    • @skwee:使用kill()(参见man 2 kill)命令向进程发送信号。此外,您可能需要在要发送的信号的接收过程中添加一个信号处理程序(什么都不做),因为接收该信号的默认行为可能是简单地结束整个过程。我还建议阅读man 7 signalman sigaction。还要避免使用函数signal(),因为它的行为因实现而异。在命令行上有可用的等效命令kill(请参阅man kill)。如果没有任何帮助,请添加另一个问题。
    • @skwee:请查看我更新的答案以获取一些示例 sn-ps。
    • @alk 如果我将中断处理程序安装为 SIG_IGN,而不是创建显式的空处理程序,是否一样?
    • @RobinHsu:如果信号被忽略,则不会收到。如果没有接收到信号,阻塞功能将不会被中断。所以不,忽略一个信号不等于接收它,什么也不做。
    猜你喜欢
    • 1970-01-01
    • 2020-09-03
    • 1970-01-01
    • 2019-02-16
    • 2020-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多