与bk2204概述的建议相同:只需使用poll
.如果您想拥有一个单独的线程,通知该线程的一种简单方法是将管道(或套接字)的读取端添加到轮询的文件描述符集.然后,当主线程希望线程停止时,它会关闭写入端.然后,poll
将返回并发信号通知可以从管道读取(因为它将发信号通知EOF).
以下是一项实施的概要:
我们首先为文件描述符定义一个RAII类.
#include <unistd.h>
// using pipe, close
#include <utility>
// using std::swap, std::exchange
struct FileHandle
{
int fd;
constexpr FileHandle(int fd=-1) noexcept
: fd(fd)
{}
FileHandle(FileHandle&& o) noexcept
: fd(std::exchange(o.fd, -1))
{}
~FileHandle()
{
if(fd >= 0)
::close(fd);
}
void swap(FileHandle& o) noexcept
{
using std::swap;
swap(fd, o.fd);
}
FileHandle& operator=(FileHandle&& o) noexcept
{
FileHandle tmp = std::move(o);
swap(tmp);
return *this;
}
operator bool() const noexcept
{ return fd >= 0; }
void reset(int fd=-1) noexcept
{ *this = FileHandle(fd); }
void close() noexcept
{ reset(); }
};
然后,我们使用它来构建管道或插座对.
#include <cerrno>
#include <system_error>
struct Pipe
{
FileHandle receive, send;
Pipe()
{
int fds[2];
if(pipe(fds))
throw std::system_error(errno, std::generic_category(), "pipe");
receive.reset(fds[0]);
send.reset(fds[1]);
}
};
然后,该线程在接收端使用poll
,并使用其信号fd.
#include <poll.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <cassert>
void processOSSignals(const FileHandle& stop)
{
sigset_t mask;
sigemptyset(&mask);
FileHandle sighandle{ signalfd(-1, &mask, 0) };
if(! sighandle)
throw std::system_error(errno, std::generic_category(), "signalfd");
struct pollfd fds[2];
fds[0].fd = sighandle.fd;
fds[1].fd = stop.fd;
fds[0].events = fds[1].events = POLLIN;
while(true) {
if(poll(fds, 2, -1) < 0)
throw std::system_error(errno, std::generic_category(), "poll");
if(fds[1].revents & POLLIN) // stop signalled
break;
struct signalfd_siginfo fdsi;
// will not block
assert(fds[0].revents != 0);
auto readedBytes = read(sighandle.fd, &fdsi, sizeof(fdsi));
}
}
剩下要做的就是按照这样的顺序创建各种RAII类,即在连接线程之前关闭管道的写入端.
#include <thread>
int main()
{
std::thread ossThread;
Pipe stop; // declare after thread so it is destroyed first
ossThread = std::thread(processOSSignals, std::move(stop.receive));
programLoop();
stop.send.close(); // also handled by destructor
ossThread.join();
}
其他需要注意的事项:
- 考虑切换到
std::jthread
,这样即使程序循环抛出异常,它也会自动加入
- 根据你的后台线程做什么,你也可以通过调用
std::thread::detach
在程序结束时简单地放弃它
- 如果线程可能在长循环中保持忙碌(不调用
poll
),您可以将管道与std::atomic<bool>
或jthread
的std::stop_token
配对,以发出停止事件的信号.这样,线程就可以在循环迭代之间判断标志.顺便说一句,您对普通全局int
的使用是无效的,因为您同时从不同的线程读取和写入
- 您还可以使用signalfd并向线程发送特定信号以使其退出