不能直接用 std::mutex 模拟信号量,因其仅支持单次加锁/解锁且须同线程配对,而信号量需跨线程、多次调用的计数器与阻塞等待机制;仅用 mutex 加 int 计数器而无条件变量会导致 busy-wait 或死锁。

为什么不能直接用 std::mutex 模拟信号量
因为 std::mutex 只能加锁/解锁一次,且必须由同一线程成对调用,而信号量的 wait() 和 signal() 可跨线程、可多次调用,核心是「计数器 + 阻塞等待」。直接拿互斥锁套个 int 计数器,不配条件变量的话,wait() 会忙等或死锁。
常见错误现象:wait() 中用 while (count —— CPU 占用飙高;或者没用 <code>std::condition_variable 就调 wait(),导致线程永远卡住。
- 必须搭配
std::condition_variable实现阻塞等待 - 所有对计数器的读写必须在
std::mutex保护下进行 -
signal()唤醒时用notify_one()(单个等待者)还是notify_all()(多个),取决于语义需求;一般用notify_one()更高效
wait() 被唤醒后为什么还要再检查计数器
因为 std::condition_variable::wait() 可能发生虚假唤醒(spurious wakeup),即没被 notify 也返回。所以不能只靠通知,必须在 while 循环里重检条件。
典型错误写法:if (count —— 一旦虚假唤醒,就跳过检查直接往下走,可能造成负计数或逻辑崩溃。
- 正确模式是:
while (count - 唤醒后立即消费资源:
--count;必须在锁内完成,否则多线程下竞态 - 注意:
cv.wait()会自动释放锁并挂起,返回时已重新持有锁
完整可运行的 C++11 实现(无第三方依赖)
以下是最小可行实现,支持构造时指定初始值,线程安全,符合 POSIX 信号量语义:
class Semaphore {
std::mutex mtx;
std::condition_variable cv;
int count;
public:
explicit Semaphore(int init = 0) : count(init) {}
<pre class="brush:php;toolbar:false;">void wait() {
std::unique_lock<:mutex> lock(mtx);
while (count lock(mtx);
++count;
cv.notify_one(); // 或 notify_all(),按需选择
}</:mutex>};
使用场景举例:限制最多 3 个线程并发访问某资源:
Semaphore sem(3); → 后续每个线程进入前调 sem.wait(),退出后调 sem.signal()。
性能提示:频繁争抢时,notify_one() 比 notify_all() 开销小;若存在优先级或公平性要求,需额外设计,标准库不保证唤醒顺序。
容易被忽略的边界与兼容性问题
Windows 上若用 MinGW 或旧版 MSVC(如 VS2015),std::condition_variable 对时钟支持不全,但本实现未用超时版本,所以无影响。真正要注意的是:
- 构造时传负数(如
Semaphore(-1))是合法的,表示初始已有“欠账”,首个wait()会阻塞,直到被足够多次signal()补足 -
signal()可连续调多次,使count大于初始值;这和 POSIXsem_post()一致,但不同于二值信号量(mutex) - 析构前确保无线程还在
wait(),否则cv和mtx被销毁后唤醒会导致未定义行为 —— 生产环境建议加引用计数或生命周期管理
最复杂的点不在实现本身,而在使用时机:wait() 和 signal() 的配对必须严格匹配业务逻辑,漏调、多调、跨作用域调都可能引发死锁或资源泄漏。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











