目录
[1 基本概念](#1 基本概念)
[2 条件变量](#2 条件变量)
[3 条件变量的核心操作](#3 条件变量的核心操作)
[3.1 条件变量初始化](#3.1 条件变量初始化)
[3.1.1 静态初始化(编译时初始化](#3.1.1 静态初始化(编译时初始化)
[3.1.2 动态初始化(运行时初始化)](#3.1.2 动态初始化(运行时初始化))
[3.1 ptread_cond_wait](#3.1 ptread_cond_wait)
[3.1.1 pthread_cond_wait 的工作原理](#3.1.1 pthread_cond_wait 的工作原理)
[3.1.2 常见问题](#3.1.2 常见问题)
[3.2 pthread_cond_signal](#3.2 pthread_cond_signal)
[3.2.1 基本工作原理](#3.2.1 基本工作原理)
[3.2.4 常见错误用法](#3.2.4 常见错误用法)
[3.3 pthread_cond_broadcast](#3.3 pthread_cond_broadcast)
1 基本概念
线程同步是指通过特定的机制,控制多个线程按照一定的顺序或规则访问共享资源,确保线程安全。
为什么需要线程同步:
- 数据一致性:防止多个线程同时修改共享数据导致数据损坏
- 避免竞态条件:确保操作的原子性
- 有序执行:控制线程执行的先后顺序,从而有效的防止线程饥饿
2 条件变量
条件变量(Condition Variable)是一种线程同步机制,用于让线程在某个条件不满足时进入等待状态,并在条件满足时被唤醒继续执行。它通常与互斥锁(Mutex)配合使用,以实现更复杂的线程同步逻辑。
为什么需要条件变量:
- 在多线程编程中,有时线程需要等待某个条件成立才能继续执行。如果仅使用互斥锁,线程可能需要不断轮询检查条件(忙等待),这会浪费CPU资源。
- 条件变量提供了一种高效等待机制,让线程在条件不满足时进入休眠状态,直到其他线程通知它条件可能已满足。
3 条件变量的核心操作
条件变量通常提供三个基本操作:
- wait(lock)
- 线程调用 wait 时,会释放锁并进入等待状态,直到被唤醒。
- 被唤醒后,它会重新获取锁,然后继续执行。
- 必须配合互斥锁使用,以防止竞态条件。
- signal() / notify_one()
- 唤醒一个正在等待该条件变量的线程(如果有多个线程在等待,只唤醒其中一个)。
- broadcast() / notify_all()
- 唤醒所有正在等待该条件变量的线程。
3.1 条件变量初始化
3.1.1 静态初始化(编译时初始化
cpp
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
- 使用宏定义初始化
- 总是使用默认属性
- 线程安全,不需要显式销毁
3.1.2 动态初始化(运行时初始化)
cpp
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
- cond: 指向要初始化的条件变量的指针
- attr : 指向条件变量属性对象的指针,通常设为 NULL 使用默认属性
- 返回值 :
- 成功返回 0
- 失败返回错误码(如 EAGAIN 表示资源不足,ENOMEM 表示内存不足)
cpp
pthread_cond_t cond;
// 初始化
pthread_cond_init(&cond, NULL);
// 销毁
pthread_cond_destory(&cond);
- 更灵活,可以指定属性
- 需要配合 pthread_cond_destroy 释放资源
- 可以在运行时决定初始化时机
3.1 ptread_cond_wait
pthread_cond_wait 是 POSIX 线程(pthread)库中用于条件变量等待 的核心函数,它允许线程在某个条件不满足时挂起(阻塞) ,并在条件可能满足时被唤醒。它通常与互斥锁(pthread_mutex_t)配合使用,以实现线程间的同步。
cpp
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
- cond : 指向条件变量的指针(pthread_cond_t)。
- mutex : 指向互斥锁的指针(pthread_mutex_t ),调用 pthread_cond_wait 前必须已加锁。
- 返回值 :
- 成功返回 0。
- 失败返回错误码(如 EINVAL 表示参数无效)
3.1.1 pthread_cond_wait 的工作原理
(1) 调用时发生了什么?
- 原子地释放 mutex(传递进来的锁资源),并让当前线程进入等待状态(阻塞)。
- 线程被移入条件变量的等待队列 ,直到被 pthread_cond_signal 或 pthread_cond_broadcast 唤醒。
- 被唤醒后,需要重新获取(争夺锁资源) mutex(可能阻塞,直到锁可用),直到争夺到锁资源,然后继续执行。
(2) 为什么需要 mutex ?
- 防止竞态条件 :检查条件和进入等待必须是原子操作 ,否则可能出现:
- 线程A检查条件(如 queue.empty() ),发现 true ,准备进入 wait。
- 线程B在A进入 wait 前修改了条件(如 queue.push() )并发出 signal。
- 线程A进入 wait ,但信号已经丢失,导致永久等待。
- pthread_cond_wait 内部会先解锁 mutex ,再进入等待 ,确保 signal 不会丢失。
3.1.2 常见问题
(1) 虚假唤醒(Spurious Wakeup)
- 线程可能在没有收到 signal 的情况下被唤醒(由于系统优化或信号中断)。
- 解决方案 :始终在 while 循环中检查条件
cpp
while (!condition) {
pthread_cond_wait(&cond, &mutex);
}
(2) 死锁风险
- 忘记解锁 :如果 pthread_cond_wait 后忘记解锁,其他线程无法获取锁。
- signal 丢失 :如果 signal 在 wait 前发出,可能导致线程永久等待。
- 解决方案 :确保 signal 发生在 wait 之后(或使用 pthread_cond_broadcast)。
(3) pthread_cond_wait 和 pthread_mutex_unlock 的顺序 - 错误用法:
cpp
pthread_mutex_unlock(&mutex);
pthread_cond_wait(&cond, &mutex); // 未持有锁时调用,行为未定义!
- 正确用法:
cpp
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex); // 内部会先解锁,再等待
pthread_mutex_unlock(&mutex);
3.2 pthread_cond_signal
pthread_cond_signal 是 POSIX 线程(pthread)库中用于唤醒等待在条件变量上的线程 的关键函数。它通常与 pthread_cond_wait配合使用,实现线程间的同步通信。
cpp
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
- cond: 指向要发送信号的条件变量的指针
- 返回值 :
- 成功返回 0
- 失败返回错误码(如 EINVAL 表示无效的条件变量)
3.2.1 基本工作原理
pthread_cond_signal 的主要作用是:
- 唤醒至少一个 正在 pthread_cond_wait 的线程(如果有多个线程在等待,具体唤醒哪个取决于实现)
- 如果没有线程在等待,这个信号会被丢弃(不会累积)
关键特性:
- 非阻塞 :调用 pthread_cond_signal 不会阻塞当前线程
- 不保证立即执行:被唤醒的线程需要重新获取互斥锁后才能继续执行
- 无记忆性 :如果调用 signal 时没有线程在等待,信号不会保存供后续使用
关键注意事项: - 调用时通常应持有锁
- 最佳实践是在持有互斥锁时调用 signal
- 这样可以避免信号丢失(在检查和等待之间的竞争条件)
- 信号可能丢失
- 如果在没有线程等待时调用 signal,信号会被丢弃
- 这通常不是问题,因为正确的模式应该使用条件变量+谓词检查
- 虚假唤醒仍然可能发生
- 即使使用 signal ,被唤醒的线程仍应该检查条件(使用 while 而不是 if)
- 性能考虑
- signal 比 broadcast 更轻量
- 在只需要唤醒一个线程的情况下优先使用 signal
底层实现机制:
pthread_cond_signal 的具体实现依赖于操作系统,但通常涉及: - 检查条件变量的等待队列
- 从队列中移出一个线程(FIFO或优先级顺序取决于实现)
- 将该线程标记为可运行状态
在Linux中,这通常通过futex(快速用户空间互斥锁)机制实现,避免了不必要的内核切换。
3.2.4 常见错误用法
(1)错误1:不加锁调用
cpp
// 错误!可能导致竞争条件
pthread_cond_signal(&cond);
应该:
cpp
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
(2)错误2:使用if而不是while检查条件
cpp
// 错误!可能因虚假唤醒导致问题
if (!condition) {
pthread_cond_wait(&cond, &mutex);
}
应该:
cpp
while (!condition) {
pthread_cond_wait(&cond, &mutex);
}
3.3 pthread_cond_broadcast
|-------|---------------------|------------------------|
| 特性 | pthread_cond_signal | pthread_cond_broadcast |
| 唤醒线程数 | 至少一个 | 所有等待线程 |
| 适用场景 | 单消费者/任意一个线程处理即可 | 多消费者/所有线程都需要处理 |
| 性能影响 | 较低(只唤醒一个) | 较高(唤醒所有) |
| 使用频率 | 更常用 | 特定场景使用 |