互斥锁和条件变量必须配合使用:互斥锁保护临界区防止数据竞争,条件变量实现线程间高效等待与唤醒;二者缺一不可,单独使用均会导致竞态或效率问题。

互斥锁和条件变量不是“二选一”的工具,而是分工明确、必须配合使用的搭档。互斥锁管“能不能动”,条件变量管“什么时候动”。单独用互斥锁,线程只能靠轮询或瞎等;只用条件变量,数据可能被多个线程同时改乱——两者缺一不可。
互斥锁:守住临界区的底线
临界区是访问共享资源(比如全局计数器、链表头指针、配置缓存)的那段代码。没有互斥锁,多个线程同时执行 ++counter 这类非原子操作,结果就可能丢失更新。
- 加锁后才能进入临界区,解锁后才允许其他线程进入
- 推荐用 RAII 方式管理(如 C++ 的 std::lock_guard 或 C 的 pthread_mutex_lock/unlock 成对调用),避免忘记解锁导致死锁
- 不要在持有锁时做耗时操作(如文件读写、网络请求),否则会拖慢所有等待线程
- 初始化和销毁必须配对:pthread_mutex_init / pthread_mutex_destroy,静态初始化(PTHREAD_MUTEX_INITIALIZER)更简洁安全
条件变量:让线程聪明地等待
条件变量不保护数据,也不提供互斥。它只负责“通知”——当某个业务条件满足时(比如队列非空、任务完成、信号到达),唤醒正在等待的线程。
- 必须和互斥锁一起用:wait 前要先持锁,调用时自动释放锁并挂起;被唤醒后自动重新获取锁,再继续执行
- 用 while 而不是 if 判断条件,防止虚假唤醒(spurious wakeup)
- signal 唤醒一个等待线程,broadcast 唤醒所有——生产者-消费者中常用 signal,屏障同步场景多用 broadcast
- 通知方修改完共享状态后,再调用 notify,确保等待线程醒来时条件已真实成立
典型组合:生产者-消费者模型
这是检验两者配合是否到位的经典场景。生产者往缓冲区写数据,消费者从中取数据;缓冲区为空时消费者该停,满时生产者该等。
- 共享数据(缓冲区、size、head/tail)必须由互斥锁保护
- 两个条件变量:not_full(供生产者等待)、not_empty(供消费者等待)
- 消费者 wait(not_empty) 前已持锁,wait 返回时锁仍被持有,可安全读取数据
- 生产者 push 后,先 notify(not_empty),再 unlock ——顺序不能颠倒
常见踩坑点
很多问题不是语法错,而是逻辑误用:
- 在没持锁的情况下调用 cond_wait → 程序崩溃或未定义行为
- notify 写在 unlock 之后 → 等待线程可能被唤醒但锁还没释放,造成短暂忙等
- 多个互斥锁嵌套时加锁顺序不一致 → 死锁高发区
- 把条件变量当成“信号量”直接计数使用 → 它不记录通知次数,丢失通知很常见











