我的应用程序有一个处理操作系统信号的线程,所以不要阻塞programLoop()
。这个线程processOSSignals,基本上一直在阅读信号SIGINT、SIGTERM、SIGQUIT的文件描述符。在接收到这些信号时,loopOver
最初为true,但被设置为false。
int mSigDesc = -1;
void init()
{
// creates file descriptor for reading SIGINT, SIGTERM, SIGQUIT
// blocks signals with sigprocmask(SIG_BLOCK, &mask, nullptr)
...
mSigDesc = signalfd(mSigDesc, &mask, SFD_NONBLOCK); // OR 3rd param = 0?
}
void processOSSignals()
{
while (loopOver)
{
struct signalfd_siginfo fdsi;
auto readedBytes = read(mSigDesc, &fdsi, sizeof(fdsi));
...
}
}
int main()
{
init();
std::thread ossThread(processOSSignals);
programLoop();
ossThread.join();
}
我的问题是-mSigDesc
应该设置为阻塞模式还是非阻塞(异步)模式?
在非阻塞模式下,该线程总是很忙碌,但是一遍又一遍地阅读和返回EAGAIN
,效率很低。
在阻塞模式下,它会等待直到收到其中一个信号,但如果从未发送该信号,则ossThread将永远不会加入。
应该如何处理呢?在非阻塞模式下使用sleep(),只是偶尔尝试阅读?或者在阻塞模式下使用select(),监视mSigDesc
,只在那里有可用的东西时读取?
2条答案
按热度按时间nue99wik1#
使用阻塞I/O还是非阻塞I/O取决于您希望如何处理I/O。
通常,如果您有一个线程专门用于阅读信号文件描述符,并且您只是希望它等待直到获得信号,那么您应该使用阻塞I/O。
然而,在许多情况下,为每个I/O操作生成一个线程是效率低下的。一个线程需要一个堆栈,这可能会消耗几兆字节,而处理许多文件描述符(可能是许多不同类型)通常更有效的方法是将它们都置于非阻塞模式,并等待其中一个就绪。
select(2)
是可行的,但是在许多系统上,它被限制在一定数量的文件描述符(在Linux上,1024),并且许多程序会超过这个数量。在Linux上,epoll(7)
家族的函数也可以使用,如果你已经使用了signalfd(2)
这样的不可移植结构,你可能会更喜欢它。例如,您可能希望将信号FD作为主循环的一部分来处理,在这种情况下,将该FD作为主循环使用
poll(2)
或其他函数之一处理的FD之一可能更合适。应该避免的是在循环中旋转或使用非阻塞套接字休眠。如果使用
poll(2)
,可以指定一个超时,在此之后,如果没有文件描述符准备就绪,操作将返回0,这样就可以控制超时,而无需求助于sleep
。dgenwo3n2#
与bk2204概述的建议相同:只要使用
poll
。如果你想有一个单独的线程,一个简单的方法来通知线程是添加一个管道(或套接字)的读取端到轮询文件描述符集。主线程然后关闭写入端时,它希望线程停止。poll
然后将返回和信号,从管道读取是可能的(因为它将信号EOF)。下面是一个实现的概要:
我们首先为文件描述符定义一个RAII类。
然后我们用它来构造管道或套接字对。
然后,线程在接收端使用
poll
及其signalfd。剩下要做的就是按照一定的顺序创建各种RAII类,以便在连接线程之前关闭管道的写入端。
其他注意事项:
1.考虑切换到
std::jthread
,以便即使程序循环引发异常也能自动加入1.根据您的后台线程所做的工作,您也可以通过调用
std::thread::detach
在程序结束时简单地放弃它1.如果线程在长循环中保持忙碌(不调用
poll
),您可以将管道与std::atomic<bool>
或jthread
的std::stop_token
配对,以发出停止事件信号。这样线程就可以在循环迭代之间检查标志。顺便说一句,您使用普通全局int
是无效的,因为您同时从不同的线程读取和写入1.您还可以使用signalfd并向线程发送特定信号以使其退出