我的应用程序有一个处理OS信号的线程,所以不会阻塞programLoop().这个线程process OSSignals基本上一直在读取信号SIGINT、SIGTERM、SIGQUIT的文件描述符.在它们的接收时,loopOver最初为真,被设置为假.

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将永远不会加入.

应该如何处理呢?在非阻塞模式下使用睡眠(),是否仅偶尔try 阅读?或者可以在阻塞模式中使用SELECT()来监视mSigDesc并且只在某事时读取.那里有卖的吗?

推荐答案

与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();
}

其他需要注意的事项:

  1. 考虑切换到std::jthread,这样即使程序循环抛出异常,它也会自动加入
  2. 根据你的后台线程做什么,你也可以通过调用std::thread::detach在程序结束时简单地放弃它
  3. 如果线程可能在长循环中保持忙碌(不调用poll),您可以将管道与std::atomic<bool>jthreadstd::stop_token配对,以发出停止事件的信号.这样,线程就可以在循环迭代之间判断标志.顺便说一句,您对普通全局int的使用是无效的,因为您同时从不同的线程读取和写入
  4. 您还可以使用signalfd并向线程发送特定信号以使其退出

Linux相关问答推荐

无法在Raspberry PI 3 Model B上分配256TB的虚拟内存

C++17:G++8.5版似乎无法正确生成无符号64位伪随机整数

shell中两个日期的天数差异

"‘operator<<’匹配失败(可能是因为我的C++/GCC版本问题)"

Linux:用户态线程在执行系统调用时是否有更高的优先级?

如何验证所有 csv 文件是否具有相同的第一行?

Linux 如何使用 PCID 的值?

每次来宾重新启动后 Vagrant 执行脚本或命令(vagrant up)

使用 AWS CLI 进行 Bash - 无法找到凭证

我需要 -D_REENTRANT 和 -pthreads 吗?

如何更改某些文件模式/扩展名的权限?

在 Node.JS 中引用相对于应用程序根目录的文件的正确方法

如何测试是否存在两个文件?

通过写入 /dev/input/mice 来控制鼠标

KDE 桌面效果中的 OpenGL 和 XRender 有什么区别?

Linux命令删除.git文件夹以外的所有文件?

为什么 mmap() 比顺序 IO 快?

如何在python中找到文件或目录的所有者

将 AWK 结果分配给变量

如何让 GNU 屏幕读取 .bash_profile/.bash_rc 更改?