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

为何子线程释放std::binary_semaphore后主线程无法立即获取?

问题描述

预期主线程应能立即获取std::binary_semaphore,因为子线程会快速释放并重新获取信号量,此时主线程理应拿到信号量。但实际主线程无法立即获取,需等待子线程循环多次(可能几次或几十次)后才能拿到。使用MSVC(C++20),请问出现这种情况的原因是什么?

复现代码

#include <iostream>
#include <semaphore>
#include <thread>
#include <chrono>

std::binary_semaphore sema{ 1 };

int main()
{
    std::thread th([]
        {
            while (true)
            {
                sema.acquire();
                std::cout << "still hold\n";
                std::this_thread::sleep_for(std::chrono::milliseconds(50));
                sema.release();
            }
        });

    std::this_thread::sleep_for(std::chrono::seconds(1));

    std::cout << "try get\n";
    sema.acquire();
    std::cout << "semaphore get\n";

    std::this_thread::sleep_for(std::chrono::seconds(10000));
}

原因分析

这是线程调度特性与MSVC信号量实现细节共同导致的:

  1. 子线程的调度优先级优势
    子线程在调用sema.release()后,立刻进入下一次循环执行sema.acquire()。此时子线程处于活跃运行状态,操作系统调度器为了减少上下文切换开销,通常会优先让当前运行的线程继续执行,因此子线程能比处于挂起等待状态的主线程更快抢占信号量。

  2. MSVC信号量的非公平实现
    C++标准并未要求std::binary_semaphore必须提供公平调度(即按线程等待顺序分配信号量)。MSVC的底层实现属于非公平模式,不会优先唤醒最早等待的主线程,而是允许当前活跃的子线程反复抢占信号量。

  3. 自旋锁优化的影响
    MSVC的信号量实现可能包含自旋锁优化:当信号量被释放时,若有线程正在自旋等待(比如子线程刚释放就立刻尝试获取),会优先唤醒自旋的线程,而主线程此时是内核态挂起等待,需要额外的上下文切换才能参与竞争,进一步加剧了子线程的抢占优势。

验证与解决思路

如果要让主线程更及时地抢到信号量,可尝试:

  • 在子线程的sema.release()之后,调用std::this_thread::yield()主动让出CPU,给主线程竞争的机会。
  • 若基于Windows平台,可使用原生的CreateSemaphore并指定公平属性(但会脱离标准C++范畴)。

内容的提问来源于stack exchange,提问作者LiiiiEW

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 09:42:15