
多线程条件变量
条件变量
条件变量是用来通知共享数据的状态信息的机制。由于涉及共享数据,因此条件变量是结合 互斥量来使用的。
创建&销毁条件变量
POSIX 用pthread_cond_t 类型的变量来表示条件变量。程序必须在使用pthread_cond_t变量之前对其进行初始化。对那些静态分配的、使用默认属性的pthread_cond_t变量来说, 可以直接将PTHREAD_COND_INITIALIZER 赋给变量就可以完成初始化。对那些动态分配的或 不使用默认属性的变量来说,就要调用pthread_cond_init函数来执行初始化。
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t
*restrict attr);
- 参数attr 是一个条件变量属性对象,如果将NULL传递给attr,则初始化一个具有默认属 性的条件变量,否则,就要用与线程属性对象类似的方式,先创建一个条件变量属性对象, 再设置它。
- 如果成功,pthread_cond_init 返回 0,如果不成功,pthread_cond_init 就返 回一个非零的错误码。
函数pthread_cond_destroy 销毁一个条件变量。该函数的形式为:
int pthread_cond_destroy(pthread_cond_t *cond);
- 如果成功,pthread_cond_destroy 就返回0。如果不成功,它就返回一个非零的错误码。
等待和通知条件变量
条件变量是与断言或条件测试一同调用的,条件变量这个名称就是从这个事实中引申出来的。通常,线程会对一个断言进行测试,如果测试失败,就调用pthread_cond_wait。函数 pthread_cond_timedwait 可以用来等待一段有限的时间。这两个函数的第一个参数cond是 一个指向条件变量的指针,第二个参数mutex是一个指向互斥量的指针。
⭐线程在调用等待条件变量的函数之前,应该拥有这个互斥量。当线程被放置在条件变量的等待队列中时,等待 操作会释放这个互斥量。
pthread_cond_timedwait 函数的第三个参数是一个指向返回时间 的指针,如果条件变量信号没有在此之前出现的话,线程就在这个时间返回。注意,这个值 表示的是绝对时间,而不是时间间隔。这两个函数的形式为:
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t
*restrict mutex, const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict
mutex);
- 如果成功,这两个函数返回0。如果不成功,它们返回一个非零的错误码。
- 如果abstime指 定的时间已经到期了,pthread_cond_timedwait就返回ETIMEDOUT。
当另一个线程修改了可能会使断言成真的变量时,它应该唤醒一个或多个在等待断言成真的 线程。pthread_cond_signal 函数只唤醒一个阻塞在 cond 指向的条件变量上的线程。 pthread_cond_broadcast 函数解除所有阻塞在cond指向的条件变量上的线程。这两个函数 的形式为:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
- 如果成功,这两个函数返回0,如果不成功,它们返回一个非零的错误码。
- 等待条件变量的 线程在被唤醒后会等待对应的互斥量,在它获得了互斥量之后,它才会被复原成就绪状态。
关于条件变量设计核心的理解
关于”线程在调用等待条 件变量的函数之前,应该拥有这个互斥量。当线程被放置在条件变量的等待队列中时,等待操作会释放这个互斥量。”的理解:
- 调用
pthread_cond_wait(&cond, &mutex)前必须先lock(&mutex)—— 这是为了保护“条件判断”的原子性; wait会自动unlock(&mutex)—— 为了让其他线程能进来改条件;- 被唤醒后
wait会自动lock(&mutex)—— 保证你醒来时能安全地重新检查条件和操作共享数据。
在调用 pthread_cond_wait时要先调用pthread_mutex_lock,为的就是不让其他线程来打扰,而当发现需要进行wait操作等待条件变量时,就会自动释放锁让其他线程也有资格访问内存空间或者进行其他操作(如改变条件变量),而当被唤醒的时候,等待条件变量的 线程在被唤醒后会等待对应的互斥量,在它获得了互斥量之后,它才会被复原成就绪状态。然后又会自动上锁。
举个栗子🌰:
你是一个可可爱爱的线程A,这天你来到厨房想等“咖啡做好”:
- 你先锁住厨房门(mutex),防止别人干扰你享用美味咖啡。
- 你检查咖啡机:还没好 → 于是你决定“等”。
- 但你不能锁着门睡觉 —— 否则咖啡师(线程B)进不来做咖啡!
- 所以你把钥匙(锁)挂在门把手上(wait 自动 unlock),然后去沙发上睡觉(进入等待队列)。
- 咖啡师进来,做好咖啡,按门铃(signal),然后离开。
- 你被门铃吵醒,先拿回钥匙锁门(wait 返回时自动重新 lock),再进去喝咖啡(安全操作共享数据)。
→ 这就是 pthread_cond_wait 的精妙设计!
- “调用前有锁” → 保护条件检查不被干扰
- “等待中放锁” → 让别人能改条件发信号
- “唤醒后拿锁” → 保证醒来能安全重检条件和操作数据”
状态变量配合
条件变量必须搭配状态变量使用
子线程:
pthread_mutex_lock(&lock);
while (data_ready == 0) { // 用 while 检查状态
pthread_cond_wait(&conVar, &lock);
}
// 处理数据
printf("get str: %s\n", buff);
data_ready = 0; // 清除状态
pthread_mutex_unlock(&lock);
主线程:
pthread_mutex_lock(&lock);
memcpy(buff, tempBuff, strlen(tempBuff) + 1);
data_ready = 1; // 设置状态
pthread_cond_signal(&conVar); // 再发信号
pthread_mutex_unlock(&lock);
这样,即使子线程还没 wait,状态变量 data_ready 也会记住“有数据了”。
当子线程终于执行到 while (data_ready == 0) 时,发现 data_ready == 1,直接跳过 wait,立即处理!
信号永远不会丢失!