名词区分
- 条件变量 pthread_cond_t:运行期线程同步工具,搭配普通 C 语言while/if(不带#),用于线程等待某个事件发生。
- 条件编译 #if / #endif:编译期预处理指令,带#,仅做代码裁剪,和线程运行逻辑无关。
前置概念
1. 临界资源、临界区
- 临界资源:多个线程都可以读写的共享数据,本文示例为全局变量data。
- 临界区:操作临界资源的代码片段。
多个线程同时执行临界区,会篡改破坏共享数据,需要互斥锁做保护。
2. 互斥锁 pthread_mutex_t
作用:保证同一时刻,最多只有一个线程执行临界区代码。
- pthread_mutex_lock:加锁
- pthread_mutex_unlock:解锁
条件变量不能脱离互斥锁单独使用。条件变量负责让线程休眠等待;互斥锁用来保护被判断的共享数据。
3. 虚假唤醒
POSIX 标准许可的底层现象:线程已经休眠、挂在条件变量等待队列上,没有其他线程发送signal唤醒信号,内核却把休眠的线程唤醒。
这不是代码 Bug,普通测试很难复现,高并发压力场景才容易暴露。
4. pthread_cond_wait 执行流程
调用前提:调用该函数之前,线程必须已经持有互斥锁。
重点:函数返回,仅仅代表线程被唤醒并且拿到锁,不代表业务条件已经成立。
业务场景:生产者‑消费者
业务需求:
用到两个条件变量:
- cond_produce:生产者等待条件,数据满 100 时生产者休眠;
- cond_consume:消费者等待条件,数据不足 100 时消费者休眠。
完整示例代码:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_produce = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_consume = PTHREAD_COND_INITIALIZER;
int data = 0;
//消费者线程:等待data达到100再执行消费
void *consumer(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
while(data < 100)
{
pthread_cond_wait(&cond_consume, &mutex);
}
printf("【消费者】开始消费,data=%d\\n", data);
data = 0;
printf("【消费者】消费完成,data=%d\\n", data);
pthread_cond_signal(&cond_produce);
pthread_mutex_unlock(&mutex);
usleep(100000);
}
return NULL;
}
//生产者线程:累加data,满100就等待消费完成
void *producer(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
data++;
printf("【生产者】生产,data=%d\\n", data);
if(data >= 100)
{
printf("【生产者】数据已满,通知消费者\\n");
pthread_cond_signal(&cond_consume);
while(data >= 100)
{
pthread_cond_wait(&cond_produce, &mutex);
}
}
pthread_mutex_unlock(&mutex);
usleep(100000);
}
return NULL;
}
int main(void)
{
pthread_t tid_pro, tid_con;
pthread_create(&tid_pro, NULL, producer, NULL);
pthread_create(&tid_con, NULL, consumer, NULL);
pthread_join(tid_pro, NULL);
pthread_join(tid_con, NULL);
return 0;
}
重点讲解:为什么不能使用 if
❌错误 if 版本代码片段
pthread_mutex_lock(&mutex);
if(data < 100) // 条件只会判断一次
{
pthread_cond_wait(&cond_consume, &mutex);
}
data = 0;
pthread_mutex_unlock(&mutex);
if 版本时序(虚假唤醒场景)
✅此时线程停在pthread_cond_wait函数内部,函数还没有返回。
👉虚假唤醒就发生在这里:没有任何线程调用signal,操作系统内核直接唤醒这个休眠的线程。
✅正确 while 版本代码片段
pthread_mutex_lock(&mutex);
while(data < 100) // 每一次wait返回,都会重新校验条件
{
pthread_cond_wait(&cond_consume, &mutex);
}
data = 0;
pthread_mutex_unlock(&mutex);
while 版本对应时序
核心结论:pthread_cond_wait返回 ≠ 业务条件成立。
signal 与 broadcast
- pthread_cond_signal:唤醒等待队列中至少一个线程。
- pthread_cond_broadcast:唤醒全部正在等待的线程。
即便使用signal,也必须写while。signal 仅负责唤醒线程,无法规避虚假唤醒。 补充:虚假唤醒不属于内核 Bug。为简化内核内部实现,POSIX 将该行为合法化,把条件二次校验的责任交给上层应用代码。
标准编码范式
等待方(等待条件满足)
pthread_mutex_lock(&mutex);
while (/*条件不满足*/)
{
pthread_cond_wait(&cond, &mutex);
}
//操作共享资源
pthread_mutex_unlock(&mutex);
唤醒方(修改条件、发出通知)
pthread_mutex_lock(&mutex);
//修改共享数据
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);





