在 std::thread 创建的线程中调用 pthread_sigmask 是一种好习惯吗?

Posted

技术标签:

【中文标题】在 std::thread 创建的线程中调用 pthread_sigmask 是一种好习惯吗?【英文标题】:Is it a good practice to call pthread_sigmask in a thread created by std::thread? 【发布时间】:2019-02-25 16:54:37 【问题描述】:

1) 我是 std::thread 的新手,我想知道调用pthread_sigmask() 来阻止@987654322 创建的特定 线程中的一些信号是否是一个好习惯@。

我不希望新线程接收SIGTERM、SIGHUP等信号,因为主进程已经为这些信号安装了处理程序。

那么,调用pthread_sigmask() 来阻止std::thread 创建的线程中的一些信号是一种好习惯吗?

2) 另外,我相信pthread_sigmask(SIG_BLOCK, &mask, NULL) 的效果只适用于使用创建的线程

std::thread(&Log::rotate_log, this, _logfile, _max_files, _compress).detach();

并调用rotate_log() 作为启动函数。

并且pthread_sigmask(SIG_BLOCK, &mask, NULL)的效果将不适用于调用std::thread(&Log::rotate_log, this, _logfile, _max_files, _compress).detach()的线程。

我的理解正确吗?

void rotate_log (std::string logfile, uint32_t max_files, bool compress)

    sigset_t mask;

    sigemptyset (&mask);
    sigaddset (&mask, SIGTERM);
    sigaddset (&mask, SIGHUP);
    pthread_sigmask(SIG_BLOCK, &mask, NULL);

    // Do other stuff.


void Log::log (std::string message)
    
        // Lock using mutex
        std::lock_guard<std::mutex> lck(mtx);

        _outputFile << message << std::endl;
        _outputFile.flush();
        _sequence_number++;
        _curr_file_size = _outputFile.tellp();

        if (_curr_file_size >= max_size) 
            // Code to close the file stream, rename the file, and reopen
            ...


            // Create an independent thread to compress the file since
            // it takes some time to compress huge files.
            if (!_log_compression_on)
            
                std::thread(&Log::rotate_log, this, _logfile, _max_files, _compress).detach();
            
        
    

【问题讨论】:

这至少会假设 std::thread 是根据 pthread 实现的,但情况可能并非如此。 我已经在我的问题中提到了目的是“我不希望新线程接收诸如SIGTERM,SIGHUP等信号,因为主进程已经为这些信号安装了处理程序。”现在,在这种情况下,我想知道上面的例子是否符合这个目的,以及我所做的是否是一个好的做法。 【参考方案1】:

理论上,即使在具有 POSIX 线程的系统上,std::thread 的实现也可能会创建非 POSIX 线程,而pthread_sigmask 不适用于此类线程。 (不过Maxim Egorushkin's comment 是正确的——您确实应该在创建线程的线程中阻塞信号,并且只取消阻塞您希望在新线程上处理的信号,以避免出现竞争条件。)

我不能代表其他实现,但对于 GNU/Linux 实现来说,不太可能发生这样的事情。当然,我也不能权威地谈论这个实现,但是有太多的 C 和 C++ 代码假设用户空间线程(无论是 C、C++ 还是 POSIX)和内核任务(那些有 TID)。十年前,人们仍然认为 n:m 线程库是可能的(POSIX 线程的早期 1:m 实现只是一个特例)。

但是今天,程序员从一个线程调用unshare (CLONE_FS) 为该线程提供一个私有的当前目录,与所有其他线程分开。他们调用setfscreatecon 并期望这只影响调用线程。他们甚至直接调用系统调用setresuidsetresgid,因为他们希望避免glibc 用来将更改传播到所有线程的setxid 广播(内核不直接支持的东西)。所有这些都将在 n:m 线程模型下停止工作。所以std::thread 和 POSIX 线程必须映射到内核任务,强制执行 1:1 模型。

此外,C 和 C++ 都只有一个 GNU TLS ABI,而这又要求系统中只能有一种类型的线程,一个线程指针用于最终到达线程-本地数据。

这就是为什么在 GNU/Linux 上,std::thread 不太可能使用 glibc 提供的 POSIX 线程以外的任何东西。

【讨论】:

在 Linux 上也可以使用signalfd 来获得更好的信号处理方式。【参考方案2】:

正确的方法是在创建线程之前在父级中设置所需的信号掩码,然后在父级中将其还原。这样,您新创建的线程从一开始就具有正确的信号掩码集。 (信号掩码继承自父线程)。

当您在线程启动后设置信号掩码时,有一个时间窗口,在此期间线程没有所需的掩码。

【讨论】:

【参考方案3】:

如果您需要在 POSIX 系统上的多线程程序中设置信号掩码,那么pthread_sigmask 就是您需要使用的函数。 C++ 标准库中没有与信号掩码交互的函数。

另外,我相信pthread_sigmask(SIG_BLOCK, &amp;mask, NULL) 的效果只适用于使用...创建的线程

pthread_sigmask 适用于执行调用的线程,无论线程是如何创建的。它不适用于任何其他预先存在的线程。在您的示例中,调用pthread_sigmask 的函数rotate_logstd::thread 创建的线程中执行。

【讨论】:

以上是关于在 std::thread 创建的线程中调用 pthread_sigmask 是一种好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章

C++ std::thread 无效使用 void 表达式

如何使用 QThread 创建一个分离的线程,就像在 std::thread 中一样

c++ std::thread 与静态成员挂起

如何使用 std::thread C++ 生成多个调用相同函数的线程 [关闭]

如何销毁由 std::thread 显式创建的线程?

在运行之前获取 std::thread 的 thread:id?