You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

创建线程前调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 07:38:26