嵌入式Linux开发中,线程同步是绕不开的核心场景——消息队列数据就绪、外设状态变化、多线程资源协作,这些场景下你是否遇到过这些问题?
- 用轮询判断条件,CPU占用率飙升;用sleep做延时等待,要么响应不及时,要么浪费资源;多线程协作时出现竞态条件,逻辑错乱;
而pthread条件变量正是解决线程等待-通知场景的最优解——它能让线程在条件不满足时休眠,条件满足时精准唤醒,既保证响应速度,又能最大化降低CPU占用。
一、核心原理
1.1 条件变量的本质
条件变量是pthread库提供的线程间通知机制,用于传递“特定条件已满足”的信号。它本身不存储数据,也不保证互斥,仅负责线程的休眠与唤醒,必须与互斥锁配合使用。它解决了等待某个条件成立这个特定场景的线程同步问题。
pthread库条件变量API:
条件变量(Condition Variable)不是锁,而是一个线程等待队列的抽象。它在内核中维护了一个休眠线程链表,当线程调用pthread_cond_wait()时:
- 线程从用户态陷入内核态内核将其状态从RUNNING改为WAITING线程被加入条件变量的等待队列调度器切换到其他就绪线程
当其他线程调用pthread_cond_signal()时,内核会从等待队列中选择一个线程,将其状态改为READY,等待调度器分配CPU时间。
1.2 为什么必须和互斥锁搭配?
这个生产者-消费者场景:
错误场景:没有互斥锁保护
检查条件和进入等待这两个操作必须是原子的,否则会出现lost wakeup(唤醒丢失)。
正确场景:使用互斥锁保证原子性
互斥锁配合条件变量确保了从"持有锁检查条件"到"释放锁并休眠"这个过程的原子性,生产者在消费者真正进入等待后才能获取锁并发送信号。
1.3 pthread_cond_wait为什么需要传递互斥锁?
这是条件变量设计的核心,本质是实现“解锁-休眠”的原子操作:
- 传递互斥锁后,pthread_cond_wait会先自动释放锁,再让线程休眠——避免线程持有锁休眠,导致其他线程无法修改条件。实际pthread_cond_wait的应用例子如:
**pthread_cond_wait()**在内部原子地完成三个动作:
- 线程被唤醒后,会先自动重新获取锁,再返回用户态——确保后续检查条件、执行逻辑时,依然是线程安全的。
如果不传递互斥锁,用户需要手动解锁和加锁,但“解锁”和“休眠”之间会存在间隙,必然导致竞态条件。
1.4 pthread_cond_signal和pthread_cond_broadcast的区别?
| 特性 | pthread_cond_signal | pthread_cond_broadcast |
|---|---|---|
| 唤醒范围 | 唤醒至少一个等待线程(具体哪个由调度器决定) | 唤醒所有等待线程 |
| 资源开销 | 低(仅唤醒一个,调度成本低) | 高(唤醒所有,可能引发惊群效应) |
| 适用场景 | 一对一通知(一个线程修改条件,一个线程等待) | 一对多通知(一个线程修改条件,多个线程等待) |
1.5. 为什么用while而不是if判断条件?
核心原因:虚假唤醒和条件变化。
- 虚假唤醒:操作系统可能在没有收到signal/broadcast的情况下,唤醒等待的线程(如系统调度优化、信号中断等);条件变化:多个等待线程被唤醒后,其中一个线程可能已经修改了条件(如消费了队列中的数据),其他线程的条件已不再满足。
反例(用if导致bug):
正例(while循环):
无论是否是虚假唤醒,都会重新检查条件,确保只有条件真正满足时才执行后续逻辑。
二、使用条件变量的典型步骤
等待方步骤:
- 加互斥锁(pthread_mutex_lock);循环检查条件(while(condition == false));条件不满足时,调用pthread_cond_wait休眠;被唤醒后,重新检查条件,执行业务逻辑;解锁互斥锁(pthread_mutex_unlock)。
通知方步骤:
- 加互斥锁(pthread_mutex_lock);修改条件(如:设置flag为true、添加数据到队列);发送通知(pthread_cond_signal或pthread_cond_broadcast);解锁互斥锁(pthread_mutex_unlock)。
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
staticpthread_t s_thread1_id;
staticpthread_t s_thread2_id;
staticvolatileunsignedchar s_thread1_running = 0;
staticvolatileunsignedchar s_thread2_running = 0;
staticpthread_mutex_t s_mutex;
staticpthread_cond_t s_cond;
staticint s_cnt = 0;
staticint s_processed = 0; // 标记是否已处理,避免重复处理
// 通知方:
// 1. 加互斥锁(pthread_mutex_lock);
// 2. 修改条件(如:设置flag为true、添加数据到队列);
// 3. 发送通知(pthread_cond_signal或pthread_cond_broadcast);
// 4. 解锁互斥锁(pthread_mutex_unlock)。
void *thread1_fun(void *arg)
{
s_thread1_running = 1;
while (s_thread1_running)
{
pthread_mutex_lock(&s_mutex); // 加锁
s_cnt++; // 修改条件
if (s_cnt % 5 == 0)
{
s_processed = 0; // 重置处理标志
pthread_cond_signal(&s_cond); // 发送通知唤醒等待线程
printf("[%s] s_cnt = %d, 发送信号n", __FUNCTION__, s_cnt);
}
else
{
printf("[%s] s_cnt = %dn", __FUNCTION__, s_cnt);
}
pthread_mutex_unlock(&s_mutex); // 解锁
usleep(100 * 1000);
}
pthread_exit(NULL);
}
// 等待方:
// 1. 加互斥锁(pthread_mutex_lock);
// 2. 循环检查条件(while(condition == false));
// 3. 条件不满足时,调用pthread_cond_wait休眠;
// 4. 被唤醒后,重新检查条件,执行业务逻辑;
// 5. 解锁互斥锁(pthread_mutex_unlock)。
void *thread2_fun(void *arg)
{
s_thread2_running = 1;
while (s_thread2_running)
{
pthread_mutex_lock(&s_mutex); // 加锁
// 等待条件:s_cnt能被5整除且未被处理
while (s_cnt % 5 != 0 || s_processed)
{
pthread_cond_wait(&s_cond, &s_mutex); // 等待条件变量
}
// 条件满足,处理数据
printf("[%s] s_cnt = %d, 条件满足,开始处理n", __FUNCTION__, s_cnt);
s_processed = 1; // 标记已处理
pthread_mutex_unlock(&s_mutex); // 解锁
usleep(200 * 1000);
}
pthread_exit(NULL);
}
int main(void)
{
int ret = 0;
// 创建互斥量
ret = pthread_mutex_init(&s_mutex, NULL);
if (ret != 0)
{
printf("pthread_mutex_init error!n");
exit(EXIT_FAILURE);
}
// 创建条件变量
ret = pthread_cond_init(&s_cond, NULL);
if (ret != 0)
{
printf("pthread_cond_init error!n");
exit(EXIT_FAILURE);
}
// 创建线程1(通知方/生产者)
ret = pthread_create(&s_thread1_id, NULL, thread1_fun, NULL);
if (ret != 0)
{
printf("thread1_create error!n");
exit(EXIT_FAILURE);
}
// 创建线程2(等待方/消费者)
ret = pthread_create(&s_thread2_id, NULL, thread2_fun, NULL);
if (ret != 0)
{
printf("thread2_create error!n");
exit(EXIT_FAILURE);
}
// 运行10秒后退出
sleep(10);
s_thread1_running = 0;
s_thread2_running = 0;
pthread_join(s_thread1_id, NULL);
pthread_join(s_thread2_id, NULL);
pthread_mutex_destroy(&s_mutex);
pthread_cond_destroy(&s_cond);
printf("程序正常退出n");
return0;
}
实际应用注意事项:
不要在持有多个锁时调用pthread_cond_wait——唤醒后重新加锁的顺序可能导致死锁;
确保条件变量和互斥锁的生命周期一致——避免在销毁后仍有线程调用wait/signal;
避免“惊群效应”——broadcast会唤醒所有等待线程,若线程数量多,可拆分条件变量,按功能分组唤醒。
三、总结
pthread条件变量的核心价值是“高效的等待-通知”,解决了轮询和sleep的资源浪费问题。其使用的关键的是把握3点:互斥锁的原子配合、while循环检查条件、signal/broadcast的场景匹配。
720