为何子线程释放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信号量实现细节共同导致的:
子线程的调度优先级优势
子线程在调用sema.release()后,立刻进入下一次循环执行sema.acquire()。此时子线程处于活跃运行状态,操作系统调度器为了减少上下文切换开销,通常会优先让当前运行的线程继续执行,因此子线程能比处于挂起等待状态的主线程更快抢占信号量。MSVC信号量的非公平实现
C++标准并未要求std::binary_semaphore必须提供公平调度(即按线程等待顺序分配信号量)。MSVC的底层实现属于非公平模式,不会优先唤醒最早等待的主线程,而是允许当前活跃的子线程反复抢占信号量。自旋锁优化的影响
MSVC的信号量实现可能包含自旋锁优化:当信号量被释放时,若有线程正在自旋等待(比如子线程刚释放就立刻尝试获取),会优先唤醒自旋的线程,而主线程此时是内核态挂起等待,需要额外的上下文切换才能参与竞争,进一步加剧了子线程的抢占优势。
验证与解决思路
如果要让主线程更及时地抢到信号量,可尝试:
- 在子线程的
sema.release()之后,调用std::this_thread::yield()主动让出CPU,给主线程竞争的机会。 - 若基于Windows平台,可使用原生的
CreateSemaphore并指定公平属性(但会脱离标准C++范畴)。
内容的提问来源于stack exchange,提问作者LiiiiEW
相关产品推荐
相关产品推荐

