线程同步与互斥核心要点整理
线程同步与互斥核心要点整理
一、同步与互斥基础
-
核心问题:
- 数据竞争:多线程共享资源(全局变量、文件、数据库)时,CPU时间片轮询可能导致数据错乱。
- 示例:银行账户操作中,存钱与取钱线程并发执行可能破坏余额一致性。
-
关键概念:
- 互斥:同一时间仅一个线程访问临界资源,通过互斥锁实现。
- 同步:控制线程执行顺序,通过信号量、条件变量实现。
- 临界资源:多线程共享的资源(如
int money
)。 - 临界区:操作临界资源的代码段(如修改
money
的代码)。
二、互斥锁(pthread_mutex_t
)
-
核心函数:
函数 功能 参数说明 pthread_mutex_init()
初始化锁(静态/动态) 锁指针、属性(NULL为普通锁) pthread_mutex_lock()
阻塞式加锁 锁指针 pthread_mutex_trylock()
非阻塞尝试加锁 锁指针 pthread_mutex_unlock()
解锁 锁指针 pthread_mutex_destroy()
销毁锁 锁指针 -
特性:
- 普通锁:重复加锁会导致死锁。
- 递归锁:允许同一线程多次加锁,通过属性
PTHREAD_MUTEX_RECURSIVE_NP
设置。 - 代码示例:
c
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
// 临界区操作
pthread_mutex_unlock(&mutex);
三、死锁与递归锁
-
常见死锁场景:
- 重复加锁:同一线程对同一锁多次加锁。
- 交叉加锁:线程A持有锁1请求锁2,线程B持有锁2请求锁1。
- 未释放锁:线程异常退出未解锁。
-
递归锁解决方案:
- 初始化递归锁:
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&mutex, &attr);
- 递归锁示例:允许函数递归调用时多次加锁。
- 初始化递归锁:
四、信号量(sem_t
)
-
核心函数:
函数 功能 参数说明 sem_init()
初始化信号量 信号量指针、共享标志(0为线程共享)、初始值 sem_wait()
P操作(信号量减1,阻塞) 信号量指针 sem_post()
V操作(信号量加1) 信号量指针 sem_destroy()
销毁信号量 信号量指针 -
应用场景:
- 控制并发线程数:如允许多个线程同时写入日志文件。
- 生产者-消费者模型:通过信号量协调生产与消费的节奏。
- 代码示例:
sem_t sem;
sem_init(&sem, 0, 3); // 允许3个线程并发访问
sem_wait(&sem); // 进入临界区
sem_post(&sem); // 退出临界区
五、条件变量(pthread_cond_t
)
-
核心函数:
函数 功能 参数说明 pthread_cond_init()
初始化条件变量 条件变量指针、属性(通常为NULL) pthread_cond_wait()
阻塞线程,释放锁并等待信号 条件变量指针、互斥锁指针 pthread_cond_signal()
唤醒一个等待线程 条件变量指针 pthread_cond_broadcast()
唤醒所有等待线程 条件变量指针 pthread_cond_destroy()
销毁条件变量 条件变量指针 -
应用场景:
- 生产者-消费者模型:生产者生产数据后唤醒消费者。
- 解决空转问题:通过标志位(如
flag
)判断是否需要等待。 - 代码示例:
pthread_cond_t cond;
pthread_mutex_t mutex;
int flag = 0;
// 生产者
pthread_mutex_lock(&mutex);
flag = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
// 消费者
pthread_mutex_lock(&mutex);
while (flag == 0) {
pthread_cond_wait(&cond, &mutex);
}
// 消费数据
pthread_mutex_unlock(&mutex);
六、线程顺序控制
- 互斥锁实现顺序控制:
- 示例:通过多把锁强制线程执行顺序(如打印ABC)。
// 线程A
pthread_mutex_lock(&a);
printf("A");
pthread_mutex_unlock(&b);
// 线程B
pthread_mutex_lock(&b);
printf("B");
pthread_mutex_unlock(&c);
// 线程C
pthread_mutex_lock(&c);
printf("C");
pthread_mutex_unlock(&a);
- 示例:通过多把锁强制线程执行顺序(如打印ABC)。
七、生产者-消费者模型优化
- 最终版代码逻辑:
- 双条件变量:生产者和消费者使用不同的条件变量(
cond
和cond1
)。 - 共用互斥锁:通过
flag
标志位和循环检查避免空转。 - 核心代码:
// 生产者
pthread_mutex_lock(&mutex);
while (flag != 0) pthread_cond_wait(&cond1, &mutex);
flag = 1;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
// 消费者
pthread_mutex_lock(&mutex);
while (flag != 1) pthread_cond_wait(&cond, &mutex);
flag = 0;
pthread_cond_signal(&cond1);
pthread_mutex_unlock(&mutex);
- 双条件变量:生产者和消费者使用不同的条件变量(
八、机制对比与最佳实践
机制 | 用途 | 特点 |
---|---|---|
互斥锁 | 保证临界资源独占访问 | 简单高效,但无法控制顺序 |
信号量 | 控制并发线程数量 | 可设置资源池大小(如连接池) |
条件变量 | 线程间状态通知与协作 | 需配合互斥锁,解决复杂同步问题 |
最佳实践:
- 避免死锁:按固定顺序请求锁,使用超时机制(如
pthread_mutex_trylock
)。 - 减少锁粒度:仅对必要代码段加锁,缩短临界区。
- 优先使用高层同步机制:条件变量适合复杂同步逻辑,信号量适合资源池控制。