线程同步事故主因是基础边界失守:临界区须覆盖全部共享操作,锁粒度与生命周期需匹配资源范围,初始化销毁须线程安全,等待须用while循环、设超时并正确唤醒。
线程同步组件出事故,往往不是因为用了多复杂的锁,而是几个基础边界没守牢。比如一个简单的 计数器 加了互斥锁,结果还是输出错乱;或者用信号量控制资源访问,程序却卡死不动——问题常藏在“看似安全”的临界区之外。
临界区必须包裹全部共享操作
很多人以为只要把 count++ 包进锁里就万事大吉,但忽略了它实际由三步组成:读取、自增、写回。如果只锁其中一步(比如只锁写回),或漏掉前置/后置的判断逻辑,就会出问题。
- 错误示例:先判断
if (count > 0),再加锁执行count--—— 判断和修改之间存在时间窗口,多个线程可能同时通过判断,导致超卖 - 正确做法:把整个“检查+修改”逻辑放进同一把锁的保护范围内
- 特别注意:日志打印、异常抛出、回调调用等看似无关的语句,一旦涉及共享状态(如统计失败次数),也得纳入临界区
锁的粒度与生命周期要匹配资源范围
一把锁不该管它不该管的事,也不该活过它该服务的资源周期。初学者容易犯两类典型错误:
- 锁太粗:用一个全局锁保护所有业务逻辑,导致高并发下严重串行,性能骤降甚至假死
- 锁太细或过早释放:比如在对象构造完成前就释放锁,或在资源尚未初始化完毕时允许其他线程访问
- 对象销毁时未确保锁已释放:C++ 中析构函数里仍持有锁,或 Java 中 finalize 阶段尝试加锁,极易引发死锁或崩溃
初始化与销毁必须成对且线程安全
同步组件本身不是天生线程安全的。比如 pthread_mutex_t 在使用前必须调用 pthread_mutex_init(),而这个初始化过程若被多个线程并发触发,就可能写坏内部状态。
- 静态初始化(如
PTHREAD_MUTEX_INITIALIZER)仅适用于全局/静态变量 - 动态分配的锁、条件变量、信号量,必须确保初始化发生在任何线程访问之前,且仅执行一次
- 销毁操作(如
pthread_mutex_destroy())前,必须确认没有线程仍在等待或持有该锁,否则行为未定义
等待动作必须搭配正确的唤醒与超时机制
用条件变量或信号量做等待时,不检查唤醒条件、不设超时、不处理虚假唤醒,是事故高发区。
- 永远用
while而非if检查条件,防止虚假唤醒后直接往下执行 - 所有阻塞等待都应设合理超时(如
pthread_cond_timedwait),避免因信号丢失或逻辑缺陷导致永久挂起 - 唤醒操作(
signal/broadcast)需在持有同一把互斥锁的前提下执行,否则可能唤醒正在加锁途中的线程,造成状态不一致











