信号量和互斥量语义不同、用途隔离:std::mutex用于保护临界区,具线程所有权,仅加锁线程可解锁;std::counting_semaphore用于资源计数,无所有权约束,任意线程可release。

信号量和互斥量不是“能互相替换的两种锁”,而是语义不同、用途隔离的同步原语。用错一个,轻则逻辑难维护,重则引发数据竞争或死锁。
std::mutex 是为“保护临界区”设计的,必须由同一线程 lock/unlock
它隐含线程所有权:只有加锁的线程才能解锁,否则行为未定义(std::mutex 不检查,但 pthread_mutex_t 在 debug 模式下可能报 EPERM)。这直接防止了“A 线程加锁,B 线程误释放”的错误。
- 不支持跨线程释放 —— 这是安全边界,不是限制
- 不带计数功能 —— 它只表达“被占用 / 空闲”两种状态
- RAII 封装推荐用
std::lock_guard或std::unique_lock,避免手动unlock()遗漏 - 若需递归加锁(比如函数调用链中多次进入同一临界区),得换用
std::recursive_mutex,而非硬套std::mutex
std::counting_semaphore(C++20)是为“资源池计数”设计的,无所有权约束
它本质是一个原子整数 + 等待队列:acquire() 减 1,release() 加 1。任意线程都能调用 release(),哪怕它从没 acquire() 过 —— 这正是它适合生产者-消费者模型的原因,但也意味着你无法靠它保证“谁访问了谁负责清理”。
- 初始化值可以是任意非负整数(如
std::counting_semaphore表示最多 5 个并发访问) - 没有“当前持有者”概念,所以不能用于替代互斥量来保护变量修改
- C++20 前没有标准信号量,有人用
std::condition_variable+std::mutex模拟,但易出错且性能差 - 不要用
std::counting_semaphore替代std::mutex:语义不清、无所有权保障、且某些平台实现开销更大
常见误用:把信号量当互斥量用,结果出现数据竞争
典型错误是让线程 A acquire() 后操作共享变量,却由线程 B 调用 release() 来“通知完成”。此时若线程 C 在 A 还没写完时就 acquire() 成功,就会读到中间态数据。
- 互斥量的 lock/unlock 必须成对出现在同一作用域或至少同一栈帧中(RAII 自动保证)
- 信号量的 acquire/release 可以跨函数、跨线程、甚至跨模块 —— 灵活,但责任全在开发者
- 调试时看到
shared_data值偶尔异常?先查所有修改点是否都受同一std::mutex保护,而不是靠信号量“计数协调”
最常被忽略的一点:互斥量解决的是“能不能进”,信号量解决的是“还能进几个”。前者是排他性控制,后者是容量型协调 —— 混用它们,等于拿交通红绿灯去管理停车场剩余车位。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











