创建线程前调用pthread_sigmask是否线程安全?竞态问题分析
问题背景
在CentOS环境下的C++17代码中,我通过父线程预先阻塞所有信号→创建新线程(继承信号掩码)→恢复父线程旧信号掩码的方式,实现仅主线程处理信号的需求。为此编写了RAII类ScopedThreadSignalBlocker,目前运行看似正常,但存在疑问:
- 线程创建是同步还是异步?
- 是否可能父线程恢复掩码远早于新线程完成掩码复制?
- 若存在竞态,如何为
pthread和基于pthread的std::thread提供优雅通用的解决方案?
现有ScopedThreadSignalBlocker实现
class ScopedThreadSignalBlocker { sigset_t m_oldSignalSet; auto blockAllSignalsInThreadViaSignalMaskTS() -> sigset_t { sigset_t oldMask; pthread_sigmask( SIG_UNBLOCK, NULL, &oldMask ); sigset_t setMask; sigfillset( &setMask ); pthread_sigmask( SIG_SETMASK, &setMask, NULL ); return oldMask; } auto setSignalMaskForThreadTS( const sigset_t &setMask ) -> void { pthread_sigmask( SIG_SETMASK, &setMask, NULL ); } public: explicit ScopedThreadSignalBlocker() { m_oldSignalSet = blockAllSignalsInThreadViaSignalMaskTS(); } ~ScopedThreadSignalBlocker() { setSignalMaskForThreadTS( m_oldSignalSet ); } };
使用示例
//启动pthread { ScopedThreadSignalBlocker oSignalBlocker; pthread_create( &ptUpdateThread, NULL, fnUpdateThread, NULL); } //<- pthread_create是否会在析构函数触发前完成掩码复制? //启动std::thread { ScopedThreadSignalBlocker oSignalBlocker; m_thread = std::thread{ std::move( func ) }; } //<- 同样的疑问
核心问题分析
1. 线程创建的同步特性
pthread_create是同步调用:它会在新线程的内核结构完全初始化(包括继承父线程当前的信号掩码)后才返回。基于pthread实现的std::thread构造函数同理,内部最终会调用pthread_create,同样遵循同步返回规则。
2. 竞态条件是否存在?
你的现有写法不存在竞态:
- 当
pthread_create/std::thread构造函数返回时,新线程已经完成了信号掩码的复制,父线程后续恢复自己的掩码不会影响新线程的掩码配置。
不过现有代码存在一个小错误:blockAllSignalsInThreadViaSignalMaskTS函数中,用SIG_UNBLOCK模式获取当前掩码是非法的(SIG_UNBLOCK要求第一个参数不能为NULL),正确获取当前掩码的方式是:
pthread_sigmask(SIG_SETMASK, NULL, &oldMask);
优雅通用的解决方案
以下是优化后的健壮实现,同时支持pthread和std::thread:
优化后的RAII信号阻塞类
#include <system_error> #include <pthread.h> class ScopedSignalBlocker { public: // 可指定阻塞的信号集,默认阻塞所有信号 explicit ScopedSignalBlocker(const sigset_t* sigset = nullptr) { // 保存当前信号掩码 if (pthread_sigmask(SIG_SETMASK, nullptr, &old_mask_) != 0) { throw std::system_error(errno, std::generic_category(), "Failed to get signal mask"); } sigset_t block_set; if (sigset != nullptr) { block_set = *sigset; } else { sigfillset(&block_set); } // 设置新的信号掩码 if (pthread_sigmask(SIG_SETMASK, &block_set, nullptr) != 0) { throw std::system_error(errno, std::generic_category(), "Failed to set signal mask"); } } ~ScopedSignalBlocker() noexcept { // 恢复旧掩码,析构函数不抛异常,忽略错误 pthread_sigmask(SIG_SETMASK, &old_mask_, nullptr); } // 禁止拷贝和移动,避免掩码管理混乱 ScopedSignalBlocker(const ScopedSignalBlocker&) = delete; ScopedSignalBlocker& operator=(const ScopedSignalBlocker&) = delete; ScopedSignalBlocker(ScopedSignalBlocker&&) = delete; ScopedSignalBlocker& operator=(ScopedSignalBlocker&&) = delete; private: sigset_t old_mask_; };
通用线程创建封装
为了避免重复编写RAII块,封装工具函数自动处理信号阻塞逻辑:
针对pthread的封装
#include <tuple> template<typename Func, typename... Args> int create_blocked_pthread(pthread_t* thread, const pthread_attr_t* attr, Func func, Args... args) { ScopedSignalBlocker blocker; // 用tuple传递多参数,线程函数需自行解析 return pthread_create(thread, attr, reinterpret_cast<void*(*)(void*)>(func), new std::tuple<Args...>(std::forward<Args>(args)...)); }
针对std::thread的封装
#include <thread> template<typename Func, typename... Args> std::thread make_blocked_thread(Func&& func, Args&&... args) { ScopedSignalBlocker blocker; return std::thread(std::forward<Func>(func), std::forward<Args>(args)...); }
使用示例
// 创建pthread pthread_t ptUpdateThread; create_blocked_pthread(&ptUpdateThread, nullptr, fnUpdateThread, nullptr); // 创建std::thread auto m_thread = make_blocked_thread(std::move(func));
关键注意事项
- 新线程仅继承父线程创建时刻的信号掩码,后续父线程修改掩码不会影响新线程。
- 若新线程需要调整自身信号掩码,可在线程函数内部调用
pthread_sigmask修改。 - 确保主线程正确注册信号处理函数,其他线程因掩码阻塞所有信号,不会意外处理信号。
内容的提问来源于stack exchange,提问作者SoulfreezerXP
相关产品推荐
相关产品推荐

