点击↑深色口袋物联,选择关注公众号,获取更多内容,不迷路
在glibc2.35中,pthread线程提供了5种同步机制,分别为条件变量、互斥锁、读写锁、自旋锁和屏障,本篇主要回顾条件变量
在glibc2.35中,条件变量的定义在sysdeps/nptl/bits/pthreadtypes.h中pthread_cond_t
typedef union{ struct __pthread_cond_s __data; char __size[__SIZEOF_PTHREAD_COND_T]; __extension__ long long int __align;} pthread_cond_t;其中__data是真实的数据载体,包含了操作系统所需要的全部底层信息,__size,固定大小的字符数组,保证新旧版本的二进制兼容,以及一个长长整型变量,用于控制内存对齐
为什么要用union呢?首先这种设计模式被称为 “Tagged Union” 或 “Opaque Type”(不透明类型)的一种变体。对于编译器,它看到的是一个固定大小、固定对齐方式的变量类型;对于库开发者,可以通过 __data 来灵活修改内部实现细节;对于用户,只需声明 pthread_cond_t 变量并传给函数,不需要也不应该知道内部结构。
条件变量的核心作用就是实现线程间「基于条件的有序协作」,解决「线程等待某个条件成立」的同步场景。
简单说:让某个 / 某些线程进入阻塞休眠状态,等待「指定条件满足」,当其他线程触发该条件后,再唤醒等待的线程继续执行。
它是 Linux 下 POSIX 线程库的线程同步核心机制,一般必须与互斥锁 pthread_mutex_t 配合使用,缺一不可
线程开发中,有一个高频场景:线程 A 需要等待某个条件成立才能执行(比如等待数据就绪、等待任务完成),如果没有条件变量,只能用两种低效方式实现:
sleep() 轮询:线程每隔一段时间判断条件,占用 CPU 资源,且时延不可控;while(条件) 忙等:线程持续占用 CPU 循环判断条件,CPU 利用率直接拉满,性能暴跌。条件变量完美解决以上问题,它的核心优势是:等待条件的线程会主动释放 CPU,进入内核休眠态,不占用任何 CPU 资源;条件满足时被精准唤醒,零 CPU 浪费。
很多人混淆条件变量和互斥锁的作用,这是入门核心误区,二者必须搭配使用,各司其职、缺一不可,核心分工如下:
✅ 互斥锁(pthread_mutex_t)→ 负责「互斥」
✅ 条件变量(pthread_cond_t)→ 负责「同步」
✅ 核心结论:条件变量 + 互斥锁 = 线程同步的黄金组合,能解决** 99%** 的线程有序协作场景。
条件变量的使用场景,全部围绕其核心能力:线程等待条件 + 条件满足唤醒,是线程同步中不可替代的核心机制,所有场景均为 Linux 应用层开发的高频刚需,覆盖嵌入式、服务器、工业控制等所有领域,无任何技术替代方案,按使用频率排序如下:
这是条件变量的头号使用场景,没有之一。
pthread_cond_broadcast唤醒所有业务线程;pthread_cond_timedwait替代pthread_cond_wait,设置超时时间,超时后返回ETIMEDOUT,线程执行兜底逻辑;条件变量的所有接口都在 <pthread.h> 头文件中,编译时必须加 -lpthread 链接线程库,接口数量少、逻辑简单,所有接口均有固定使用范式,掌握后零出错,按使用频率排序如下,所有接口返回值:成功返回 0,失败返回错误码。
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);cond:待初始化的条件变量地址;attr:条件变量属性,传NULL使用默认属性即可;pthread_cond_destroy销毁!int pthread_cond_destroy(pthread_cond_t *cond);int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);该接口执行时,会做「原子两步操作」,这是条件变量的核心设计,也是线程安全的保障:
mutex;pthread_cond_signal/broadcast)后,线程会自动重新获取这把互斥锁,获取成功后,才会从pthread_cond_wait返回,继续执行后续代码。int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);pthread_cond_wait功能一致,区别是增加了超时时间,超时后无论条件是否满足,都会自动返回;abstime:绝对时间(不是相对超时时间),格式是struct timespec,包含秒 + 纳秒;ETIMEDOUT,成功返回 0,失败返回其他错误码;int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);二者是互补关系,不是替代关系,所有条件变量必须搭配互斥锁使用,核心差异总结如下,一目了然:
✅ 核心结论:互斥锁解决「能不能访问」,条件变量解决「什么时候访问」。
二者都是唤醒线程的接口,属于同类型接口的选型对比,无优劣之分,按需选择即可:
✅ 选型原则:能精准唤醒,绝不广播唤醒。
二者都是等待条件的接口,核心差异是「是否有超时机制」:
✅ 生产原则:线上代码,优先使用超时等待。
代码才是硬道理。下面三个例子,涵盖了基础、超时和广播,直接拷贝到你的 ARM Linux 开发板上就能跑(记得编译加 -lpthread)。
这是最经典的用法。一个线程写数据,一个线程读数据。
#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>#define BUFFER_SIZE 5typedef struct { int buffer[BUFFER_SIZE]; int in; int out; int count;} CircularBuffer;CircularBuffer cb = {0};pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;// 生产者void* producer(void *arg) { int item = 0; while (1) { item++; pthread_mutex_lock(&lock); // 关键点1:满了就等,必须用while while (cb.count == BUFFER_SIZE) { printf("【生产者】缓冲区满了,休息一下...\n"); pthread_cond_wait(¬_full, &lock); } // 写数据 cb.buffer[cb.in] = item; cb.in = (cb.in + 1) % BUFFER_SIZE; cb.count++; printf("【生产者】放入数据 %d, 当前数量: %d\n", item, cb.count); // 唤醒消费者 pthread_cond_signal(¬_empty); pthread_mutex_unlock(&lock); sleep(1); // 模拟生产耗时 } return NULL;}// 消费者void* consumer(void *arg) { while (1) { pthread_mutex_lock(&lock); // 关键点2:空了就等,必须用while while (cb.count == 0) { printf("【消费者】缓冲区空了,等待数据...\n"); pthread_cond_wait(¬_empty, &lock); } // 取数据 int item = cb.buffer[cb.out]; cb.out = (cb.out + 1) % BUFFER_SIZE; cb.count--; printf("【消费者】取出数据 %d, 当前数量: %d\n", item, cb.count); // 唤醒生产者 pthread_cond_signal(¬_full); pthread_mutex_unlock(&lock); sleep(2); // 模拟消费耗时 } return NULL;}int main() { pthread_t pro, con; pthread_create(&pro, NULL, producer, NULL); pthread_create(&con, NULL, consumer, NULL); pthread_join(pro, NULL); pthread_join(con, NULL); pthread_mutex_destroy(&lock); pthread_cond_destroy(¬_empty); pthread_cond_destroy(¬_full); return 0;}
在实际 BSP 调试或驱动开发中,我们经常不能无限等硬件。这个例子演示了 pthread_cond_timedwait 的用法。
#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>#include <time.h>#include <errno.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int data_ready = 0;// 模拟硬件中断或另一个线程发送信号void* hardware_thread(void *arg) { printf("[硬件] 正在初始化硬件...\n"); sleep(3); // 模拟硬件初始化耗时3秒 printf("[硬件] 初始化完成!\n"); pthread_mutex_lock(&mutex); data_ready = 1; pthread_cond_signal(&cond); // 发送信号 pthread_mutex_unlock(&mutex); return NULL;}// 模拟应用层等待硬件void* app_thread(void *arg) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 2; // 只等2秒 pthread_mutex_lock(&mutex); printf("[应用] 等待硬件就绪 (超时2秒)...\n"); // 等待,带超时 int ret = pthread_cond_timedwait(&cond, &mutex, &ts); if (ret == ETIMEDOUT) { printf("[应用] 超时了!硬件可能出问题了。\n"); } else { printf("[应用] 收到信号,硬件就绪成功!\n"); } pthread_mutex_unlock(&mutex); return NULL;}int main() { pthread_t t_hw, t_app; pthread_create(&t_hw, NULL, hardware_thread, NULL); pthread_create(&t_app, NULL, app_thread, NULL); pthread_join(t_hw, NULL); pthread_join(t_app, NULL); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); return 0;}
当有多个消费者线程,但只需要一个生产者发送一次通知,所有消费者都要醒来干活时,必须用 broadcast。比如重新加载配置文件。
#include <stdio.h>#include <stdlib.h>#include <pthread.h>#include <unistd.h>pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;pthread_cond_t cond = PTHREAD_COND_INITIALIZER;int config_updated = 0;void* worker(void *arg) { int id = *(int*)arg; pthread_mutex_lock(&lock); while (config_updated == 0) { printf("[Worker %d] 正在等待配置更新...\n", id); pthread_cond_wait(&cond, &lock); // 所有线程都在这等着 } printf("[Worker %d] 收到广播!开始应用新配置...\n", id); pthread_mutex_unlock(&lock); return NULL;}void* master(void *arg) { sleep(2); // 模拟读取配置文件 pthread_mutex_lock(&lock); config_updated = 1; printf("[Master] 配置已更新,通知所有 Worker!\n"); pthread_cond_broadcast(&cond); // 一次性叫醒所有人 pthread_mutex_unlock(&lock); return NULL;}int main() { pthread_t master_tid; pthread_t workers[3]; int ids[3] = {1, 2, 3}; pthread_create(&master_tid, NULL, master, NULL); for(int i=0; i<3; i++) { pthread_create(&workers[i], NULL, worker, &ids[i]); } pthread_join(master_tid, NULL); for(int i=0; i<3; i++) { pthread_join(workers[i], NULL); } pthread_mutex_destroy(&lock); pthread_cond_destroy(&cond); return 0;}
while(条件)循环判断,可完全兼容 Linux 内核的虚假唤醒特性,线程安全;while(条件)规避,但这是内核设计特性,需要开发者主动兼容;✔️ 条件变量的优点是压倒性的,是线程同步中最优的条件等待方案,没有任何技术能替代其核心价值;
✔️ 条件变量的缺点都是可规避的,通过规范的代码编写和合理的接口选型,能轻松解决所有局限性;
✔️ 结论:条件变量是 Linux 线程同步的必学、必会、必用的核心技术,没有之一。
while(条件)包裹 wait,先加锁再 wait,唤醒必持锁,遵守这三句话,永不踩坑;pthread_cond_timedwait和pthread_cond_signal,兼顾安全性和性能。