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

SysV信号量线程交替执行异常:线程永久阻塞问题排查

问题描述

我实现了一个类似Java和.NET的监视器类,相比互斥锁+条件变量的组合效率更高。该类使用两个信号量:一个用于进入已锁定的临界区,另一个用于通知等待慢竞争路径的线程,等待中的线程会同时等待这两个信号量。
在Win32平台使用独立信号量,Linux平台则用SysV信号量集模拟Win32的WaitForMultipleObjects(),同时等待监视器的“互斥锁”部分解锁和通知信号。
我编写了C++20测试代码,创建两个各含2个信号量的信号量集,初始化setA的两个信号量值为1,启动两个线程,每个线程先等待自己信号量集的两个信号量(执行减1操作),再对对方的信号量集的两个信号量执行加1操作。但测试中两个线程均卡在首次semop调用处,陷入永久休眠,请求排查代码错误。

附测试代码:

#include <thread>
#include <initializer_list>
#include <sys/types.h>
#include <sys/sem.h>
#include <sys/stat.h>

using namespace std;

int main()
{
    int
        setA = semget( IPC_PRIVATE, 2, S_IRUSR | S_IWUSR ),
        setB = semget( IPC_PRIVATE, 2, S_IRUSR | S_IWUSR );
    if( setA == -1 || setB == -1 )
        return EXIT_FAILURE;
    static auto xSemop = []( int set, std::initializer_list<sembuf> sems )
    {
        int ret;
        while( (ret = ::semop( set, const_cast<sembuf *>(sems.begin()), sems.size() )) == EINTR );
        return ret;
    };
    if( xSemop( setA, { { 0, 1, 0 } } ) || xSemop( setA, { { 1, 1, 0 } } ) )
        return EXIT_FAILURE;
    constexpr size_t ROUNDS = 1'000'000;
    auto thr = []( int mySet, int yourSet )
    {
        for( size_t r = ROUNDS; r--; )
            if( xSemop( mySet, { { 0, -1, 0 }, { 1, -1, 0 } } )
                || xSemop( yourSet, { { 0, 1, 0 }, { 1, 1, 0 } } ) )
                terminate();
    };
    jthread
        thrA( thr, setA, setB ),
        thrB( thr, setB, setA );
}
错误原因分析
  1. setB信号量未初始化:SysV信号量集创建后,所有信号量初始值默认为0。代码仅初始化了setA的两个信号量为1,但setB的两个信号量保持0值。线程thrB启动后,尝试对setB的两个信号量执行减1操作,因信号量值不足直接阻塞。
  2. 线程执行链路阻塞:线程A首次semop成功后,会给setB的信号量加1使其变为1,理论上能唤醒线程B,但如果测试环境中线程B先于线程A启动并进入阻塞,可能会让用户误以为两个线程都卡在首次调用;另外未初始化的setB会导致整个循环链路无法正常启动。
修复方案
  1. 初始化所有信号量:创建setB后,同步初始化其两个信号量为1,确保两个线程的初始执行条件都满足:
if( xSemop( setA, { { 0, 1, 0 } } ) || xSemop( setA, { { 1, 1, 0 } } )
 || xSemop( setB, { { 0, 1, 0 } } ) || xSemop( setB, { { 1, 1, 0 } } ) )
    return EXIT_FAILURE;
  1. 更规范的初始化方式(推荐):使用semctl的SETVAL命令直接设置信号量初始值,避免依赖默认的0值:
// 部分系统需显式定义semun联合结构
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};

int main()
{
    // ... 信号量集创建逻辑 ...

    semun init_val;
    init_val.val = 1;
    // 初始化setA和setB的所有信号量为1
    if( semctl(setA, 0, SETVAL, init_val) == -1 || semctl(setA, 1, SETVAL, init_val) == -1
     || semctl(setB, 0, SETVAL, init_val) == -1 || semctl(setB, 1, SETVAL, init_val) == -1 )
        return EXIT_FAILURE;

    // ... 线程创建逻辑 ...
}
  1. 验证执行逻辑:修复初始化后,两个线程将可以正常完成100万次循环,不会出现永久阻塞。

内容的提问来源于stack exchange,提问作者Edison von Myosotis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 01:29:53