在 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
并期望这只影响调用线程。他们甚至直接调用系统调用setresuid
和setresgid
,因为他们希望避免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, &mask, NULL)
的效果只适用于使用...创建的线程
pthread_sigmask
适用于执行调用的线程,无论线程是如何创建的。它不适用于任何其他预先存在的线程。在您的示例中,调用pthread_sigmask
的函数rotate_log
在std::thread
创建的线程中执行。
【讨论】:
以上是关于在 std::thread 创建的线程中调用 pthread_sigmask 是一种好习惯吗?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 QThread 创建一个分离的线程,就像在 std::thread 中一样