取消卡在 epoll_wait 上的线程

问题描述 投票:0回答:3

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

工作线程有一个文件描述符列表,并创建一个 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(最新内核)。


谢谢!

c++ pthreads epoll
3个回答
12
投票
几乎

正确的。然而,这种差异是非常危险的。 如果你要发送信号来唤醒

epoll_wait

从不
使用epoll_wait。你必须使用 epoll_pwait,否则你的 epoll 可能会永远无法唤醒。

信号异步到达。如果您的

SIGUSR1

在您检查完关闭程序之后但在循环返回到

epoll_wait
之前到达,则信号不会中断等待(因为没有),但程序也不会退出。

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

alk 的答案的另一个问题是它没有检查

为什么

等待被中断。这可能有多种原因,其中一些与您的退出无关。 有关更多信息,请参阅

pselect

 的手册页。 
epoll_pwait 的工作原理类似。

此外,切勿使用

kill

向线程发送信号。请使用

pthread_kill
 代替。发送信号时
kill
的行为充其量是未定义的。无法保证正确的线程会收到它,这可能会导致不相关的系统调用被中断,或者根本不会发生任何事情。
    


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"); }



0
投票
pipe2()

创建一个管道(又名 FIFO),并将读取端传递给工作线程,工作线程将其添加到其 epoll 兴趣列表中。然后,当你想杀死工作线程时,你可以关闭管道的写入端。这将导致工作线程的 epoll_wait() 调用返回管道末尾的 EPOLLHUP 事件。这避免了乱搞信号的需要,信号带来了很多搬起石头砸自己脚的方法。

只是不要忘记让工作线程在终止之前关闭管道的读取端,这样就不会泄漏文件描述符。

请注意,没有人向该管道读取或写入任何内容,因此无需担心在其上设置

O_NONBLOCK

    

© www.soinside.com 2019 - 2024. All rights reserved.