互斥锁与同步锁
1. 锁的本质:解决并发问题的基石
在多线程/多进程环境中,临界区(Critical Section) 是访问共享资源的代码段。锁的核心目标是确保互斥访问——任意时刻仅有一个执行单元能进入临界区。
// 典型临界区示例
pthread_mutex_lock(&mutex);
balance = balance + 100; // 共享变量操作
pthread_mutex_unlock(&mutex);
2. 硬件级支持:原子指令的魔力
锁的底层依赖硬件提供的原子操作:
- TSL(Test and Set Lock)
原子性地读取内存值并设置为新值(通常为1)enter_region:TSL REG, LOCK ; 复制LOCK值到REG,同时设置LOCK=1CMP REG, #0 ; 检查原LOCK值JNE enter_region ; 非0则循环等待RET
- XCHG(Exchange)
原子交换寄存器与内存内容(现代CPU更常用) - CAS(Compare and Swap)
条件式原子交换(解决ABA问题)
⚠️ 硬件通过锁定内存总线确保原子性,但需注意:
- 单核CPU可用屏蔽中断实现原子性
- 多核系统必须依赖原子指令
3. 互斥锁(Mutex)的实现层级
3.1 用户空间锁(如自旋锁)
- 适用场景:临界区极短(纳秒级)
- 原理:忙等待(Busy Waiting)
- 缺陷:CPU空转浪费资源
// 基于原子指令的自旋锁 void spin_lock(int *lock) {while (__sync_lock_test_and_set(lock, 1)); }
3.2 内核辅助锁(如Futex)
- 混合架构:用户态快速路径 + 内核态慢速路径
- Linux Futex工作流程:
- 尝试原子操作获取锁(用户态)
- 若失败则调用
futex(FUTEX_WAIT)
陷入内核 - 锁释放时通过
futex(FUTEX_WAKE)
唤醒等待者
- 优势:无竞争时无需系统调用
3.3 内核级锁(如pthread_mutex)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex); // 可能触发系统调用
/* 临界区操作 */
pthread_mutex_unlock(&mutex);
- 内核行为:
- 锁空闲时直接获取
- 锁占用时线程进入睡眠态,释放CPU
4. 同步锁的经典范式:生产者-消费者模型
sem_t empty = N; // 空缓冲区数量
sem_t full = 0; // 已填充缓冲区数量
pthread_mutex_t mutex; // 缓冲区互斥锁// 生产者
void producer() {sem_wait(&empty); // 等待空位pthread_mutex_lock(&mutex);insert_item(data); // 安全写入pthread_mutex_unlock(&mutex);sem_post(&full); // 增加资源计数
}// 消费者
void consumer() {sem_wait(&full); // 等待数据pthread_mutex_lock(&mutex);data = remove_item(); // 安全取出pthread_mutex_unlock(&mutex);sem_post(&empty); // 增加空位
}
关键点:
- 互斥锁(mutex)保护共享缓冲区
- 信号量(semaphore)协调生产/消费节奏
1
5. 锁的致命陷阱与规避策略
5.1 死锁(Deadlock)
- 条件:互斥、持有等待、不可抢占、循环等待
- 解决方案:
- 锁排序(Lock Ordering)
- 超时机制(
pthread_mutex_trylock
) - 死锁检测算法(银行家算法)
5.2 优先级反转(Priority Inversion)
- 场景:低优先级线程持有锁,阻塞高优先级线程
- 解决方案:
- 优先级继承(Linux的
PTHREAD_PRIO_INHERIT
) - 优先级天花板(
PTHREAD_PRIO_PROTECT
)
- 优先级继承(Linux的