线程同步:互斥锁与条件变量实战指南
目录
一、线程同步的必要性
1. 竞态条件(Race Condition)
2. 同步机制的作用
二、互斥锁(pthread_mutex)
1. 函数原型与操作
2. 示例:保护共享变量
3. 注意事项
三、条件变量(pthread_cond)
1. 函数原型与操作
2. 示例:生产者-消费者模型
3. 注意事项
四、互斥锁与条件变量的区别
五、常见问题与解决方案
1. 死锁问题
2. 条件变量未唤醒
六、总结
一、线程同步的必要性
1. 竞态条件(Race Condition)
- 问题:多个线程并发访问共享资源(如变量、数据结构),导致结果不可预测。
- 示例:两个线程同时对全局变量
count
执行count++
,最终值可能小于预期。
2. 同步机制的作用
- 互斥锁(Mutex):确保同一时刻只有一个线程访问共享资源。
- 条件变量(Condition Variable):协调线程间的等待与唤醒(如生产者-消费者模型)。
二、互斥锁(pthread_mutex
)
1. 函数原型与操作
函数 | 作用 |
---|---|
pthread_mutex_init | 初始化互斥锁(静态或动态)。 |
pthread_mutex_lock | 加锁。若锁已被占用,则阻塞等待。 |
pthread_mutex_unlock | 解锁。允许其他线程访问共享资源。 |
pthread_mutex_destroy | 销毁互斥锁,释放资源。 |
2. 示例:保护共享变量
#include <stdio.h>
#include <pthread.h>int count = 0;
pthread_mutex_t lock;void* increment(void* arg) {for (int i = 0; i < 100000; i++) {pthread_mutex_lock(&lock); // 加锁count++;pthread_mutex_unlock(&lock); // 解锁}return NULL;
}int main() {pthread_t t1, t2;// 初始化互斥锁pthread_mutex_init(&lock, NULL);pthread_create(&t1, NULL, increment, NULL);pthread_create(&t2, NULL, increment, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);printf("Final count: %d\n", count); // 预期输出 200000// 销毁互斥锁pthread_mutex_destroy(&lock);return 0;
}
3. 注意事项
- 死锁(Deadlock):
- 同一线程重复加锁未解锁。
- 解决方法:始终按固定顺序加锁,或使用递归锁(
PTHREAD_MUTEX_RECURSIVE
)。
- 错误处理:检查所有函数返回值(如
pthread_mutex_lock
返回EDEADLK
表示死锁)。
三、条件变量(pthread_cond
)
1. 函数原型与操作
函数 | 作用 |
---|---|
pthread_cond_init | 初始化条件变量。 |
pthread_cond_wait | 等待条件满足(会自动释放锁,等待时阻塞)。 |
pthread_cond_signal | 唤醒一个等待的线程。 |
pthread_cond_broadcast | 唤醒所有等待的线程。 |
pthread_cond_destroy | 销毁条件变量。 |
2. 示例:生产者-消费者模型
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>#define BUFFER_SIZE 5int buffer[BUFFER_SIZE];
int count = 0;
pthread_mutex_t lock;
pthread_cond_t not_empty;
pthread_cond_t not_full;void* producer(void* arg) {int item = 0;while (1) {pthread_mutex_lock(&lock);while (count == BUFFER_SIZE) {pthread_cond_wait(¬_full, &lock); // 缓冲区满时等待}buffer[count++] = item++;printf("Produced %d, count = %d\n", item, count);pthread_cond_signal(¬_empty); // 通知消费者pthread_mutex_unlock(&lock);usleep(100000); // 模拟生产耗时}return NULL;
}void* consumer(void* arg) {while (1) {pthread_mutex_lock(&lock);while (count == 0) {pthread_cond_wait(¬_empty, &lock); // 缓冲区空时等待}int item = buffer[--count];printf("Consumed %d, count = %d\n", item, count);pthread_cond_signal(¬_full); // 通知生产者pthread_mutex_unlock(&lock);usleep(150000); // 模拟消费耗时}return NULL;
}int main() {pthread_t p, c;pthread_mutex_init(&lock, NULL);pthread_cond_init(¬_empty, NULL);pthread_cond_init(¬_full, NULL);pthread_create(&p, NULL, producer, NULL);pthread_create(&c, NULL, consumer, NULL);pthread_join(p, NULL);pthread_join(c, NULL);pthread_mutex_destroy(&lock);pthread_cond_destroy(¬_empty);pthread_cond_destroy(¬_full);return 0;
}
3. 注意事项
- 条件变量必须与互斥锁配合使用:
pthread_cond_wait
会自动释放锁,等待时阻塞。- 被唤醒后重新获取锁,继续执行。
- 虚假唤醒(Spurious Wakeup):
- 线程可能在未收到信号时被唤醒,需使用
while
循环检查条件。
- 线程可能在未收到信号时被唤醒,需使用
- 广播 vs 信号:
pthread_cond_broadcast
适用于“多消费者”场景,避免遗漏唤醒。
四、互斥锁与条件变量的区别
特性 | 互斥锁(Mutex) | 条件变量(Condition Variable) |
---|---|---|
作用 | 保护共享资源的独占访问 | 协调线程间的等待与唤醒 |
使用方式 | 加锁/解锁操作 | 等待/信号操作(需配合互斥锁) |
典型场景 | 防止竞态条件 | 等待特定条件成立(如缓冲区非空) |
错误处理 | 检查加锁失败(如死锁) | 检查等待失败(如中断) |
五、常见问题与解决方案
1. 死锁问题
- 原因:
- 多个线程以不同顺序加锁。
- 线程持有锁等待另一个锁。
- 解决方案:
- 固定加锁顺序:所有线程按统一顺序获取锁。
- 超时机制:使用
pthread_mutex_trylock
尝试加锁,避免无限等待。
2. 条件变量未唤醒
- 原因:
- 生产者未调用
pthread_cond_signal
。 - 消费者未进入等待状态。
- 生产者未调用
- 解决方案:
- 确保在修改条件后调用
signal
或broadcast
。 - 使用调试工具(如
gdb
)检查线程状态。
- 确保在修改条件后调用
六、总结
- 互斥锁(Mutex) 是解决竞态条件的基础工具,通过加锁/解锁保护共享资源。
- 条件变量(Condition Variable) 用于协调线程间的等待与唤醒,需配合互斥锁使用。
- 生产者-消费者模型 是条件变量的经典应用场景,通过缓冲区管理实现高效协作。
- 注意事项:
- 避免死锁,确保加锁顺序一致。
- 处理虚假唤醒,使用
while
循环检查条件。 - 错误处理:检查所有同步函数的返回值。