Linux ——条件变量

一、条件变量的核心概念

1. 为什么需要条件变量?

互斥锁解决了 "多线程互斥访问共享资源" 的问题,但无法解决 "线程需要等待某个条件满足后再执行" 的场景:

  • 比如:生产者线程生产数据,消费者线程需要等数据生产完成后才能消费;
  • 若只用互斥锁,消费者只能 "轮询检查"(不停加锁→判断条件→解锁),会浪费大量 CPU 资源;
  • 条件变量的作用 :让线程在条件不满足时阻塞等待 ,条件满足时被唤醒,避免无效轮询,提升效率。
2. 条件变量的核心特性
  • 依赖互斥锁:条件变量必须和互斥锁配合使用(保护条件判断的原子性);
  • 等待 / 唤醒机制
    • 等待:线程调用 pthread_cond_wait() 时,会自动释放互斥锁并阻塞;
    • 唤醒:其他线程调用 pthread_cond_signal()/pthread_cond_broadcast() 唤醒等待的线程,被唤醒的线程会重新获取互斥锁;
  • 虚假唤醒 :线程可能在没有被显式唤醒的情况下醒来(内核调度原因),因此条件判断必须用 while 而非 if
  • 无所有权:条件变量本身不保护资源,只负责 "等待 / 唤醒",资源保护仍需互斥锁。
3. 形象比喻

条件变量 = 公司的 "等待区" + "通知铃":

  • 互斥锁 = 等待区的门(保证同一时间只有一个人进等待区);
  • 线程(员工):想办事但条件不满足→进等待区(pthread_cond_wait)→自动开门(释放锁)→坐等通知;
  • 其他线程(管理员):条件满足→按铃(pthread_cond_signal)→唤醒等待的员工;
  • 被唤醒的员工:重新抢门(获取锁)→再次检查条件→条件满足则办事,不满足则继续等(解决虚假唤醒)。

二、条件变量核心函数(pthread 库)

Linux 条件变量的类型是 pthread_cond_t,所有函数都依赖 pthread 库(编译加 -lpthread)。

1. 条件变量初始化:pthread_cond_init()

功能:初始化一个条件变量。

函数原型
复制代码
#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

参数

  • cond:指向 pthread_cond_t 类型的指针,用于存储条件变量。
  • attr:指向 pthread_condattr_t 类型的指针,用于指定条件变量属性。通常设置为 NULL 表示使用默认属性。
  • 返回值:成功返回 0,失败返回错误码。
2. 等待条件满足:pthread_cond_wait()

功能 :阻塞等待条件变量被唤醒,且会自动释放关联的互斥锁(核心函数)。

函数原型

复制代码
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

参数

  • cond:要等待的条件变量;
  • mutex:关联的互斥锁;
  • 核心逻辑(原子操作,不可分割):
  1. 释放传入的互斥锁 mutex
  2. 阻塞当前线程,等待被唤醒;
  3. 被唤醒后,重新获取互斥锁 mutex
  4. 函数返回,线程继续执行。
  • 返回值:成功返回 0,失败返回错误码。
3. 限时等待:pthread_cond_timedwait()

功能 :和 pthread_cond_wait() 类似,但增加了超时时间,超时后自动返回。

函数原型

复制代码
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

参数

  • cond:要等待的条件变量;
  • mutex:关联的互斥锁;
  • abstime :绝对超时时间(不是相对时间),格式为 struct timespec(秒 + 纳秒);
4. 唤醒线程:pthread_cond_signal()

功能 :唤醒至少一个等待该条件变量的线程(通常唤醒队列第一个)。

函数原型

复制代码
int pthread_cond_signal(pthread_cond_t *cond);

注意

  • 唤醒后线程不会立即执行,需重新获取互斥锁;
  • 若没有线程等待,该函数无效果(不会报错)。
5. 唤醒所有线程:pthread_cond_broadcast()

功能 :唤醒所有等待该条件变量的线程。

函数原型

复制代码
int pthread_cond_broadcast(pthread_cond_t *cond);

适用场景:多个线程等待同一条件,且条件满足后所有线程都需要执行(比如 "数据更新后,所有消费者都要处理")。

6. 销毁条件变量:pthread_cond_destroy()

功能:销毁条件变量,释放相关资源。

函数原型

复制代码
int pthread_cond_destroy(pthread_cond_t *cond);

注意

  • 必须确保没有线程在等待该条件变量,否则返回 EBUSY 错误;
  • 静态初始化的条件变量也可以调用该函数(无副作用)。

三、关键注意事项

1. 必须配合互斥锁使用
  • 错误用法:直接调用 pthread_cond_wait 不加锁 → 条件判断可能被打断,导致竞态条件;
  • 核心逻辑:pthread_cond_wait 的第一个参数是条件变量,第二个是互斥锁,缺一不可。
2. 条件判断必须用 while 而非 if
  • 虚假唤醒:线程可能被内核随机唤醒(比如信号中断),此时条件并不满足;

  • if 的问题:被虚假唤醒后直接执行后续逻辑,导致操作非法资源(比如消费空缓冲区);

  • 正确写法:

    复制代码
    // 错误
    if (count == 0) pthread_cond_wait(...);
    // 正确
    while (count == 0) pthread_cond_wait(...);
3. 唤醒时机:解锁前 / 后?
  • pthread_cond_signal/broadcast 可以在加锁时调用(示例中的写法),也可以在解锁后调用;
  • 加锁时唤醒:被唤醒的线程不会立即执行(需等锁释放),但逻辑更安全;
  • 解锁后唤醒:避免 "唤醒丢失"(极少数场景),建议新手按示例写法(加锁时唤醒)。
4. 避免 "唤醒丢失"
  • 场景:线程 A 检查条件不满足 → 准备调用 pthread_cond_wait → 线程 B 此时满足条件并调用 signal → 线程 A 再调用 wait → 唤醒信号丢失,线程 A 永久阻塞;
  • 解决:条件判断和 wait 必须在锁的保护下(示例中已保证),避免上述时序问题。
5. 销毁条件变量的时机
  • 必须等所有线程都不再使用该条件变量后销毁;
  • 若有线程还在 wait 中,调用 pthread_cond_destroy 会返回 EBUSY 错误,甚至导致崩溃。

四、条件变量 vs 互斥锁(核心区别)

特性 互斥锁(pthread_mutex_t) 条件变量(pthread_cond_t)
核心作用 互斥访问共享资源 等待 / 唤醒线程(基于条件)
依赖关系 独立使用 必须配合互斥锁
阻塞方式 加锁时阻塞(抢锁失败) 主动调用 wait 阻塞
唤醒方式 无(解锁后自动竞争) signal/broadcast 主动唤醒
适用场景 保护共享资源 线程间同步(等待条件)

五、总结

  1. 条件变量核心价值:解决 "线程等待条件" 的问题,避免无效轮询,提升 CPU 利用率;
  2. 核心函数
    • 初始化:pthread_cond_init / PTHREAD_COND_INITIALIZER
    • 等待:pthread_cond_wait(必带互斥锁);
    • 唤醒:pthread_cond_signal(唤醒一个)/ pthread_cond_broadcast(唤醒所有);
    • 销毁:pthread_cond_destroy
  3. 关键规则
    • 条件变量必须和互斥锁配合使用;
    • 条件判断用 while 而非 if(解决虚假唤醒);
    • 典型场景:生产者 - 消费者模型、线程池、任务队列等;
  4. 核心逻辑:加锁→判断条件→wait(释放锁 + 阻塞)→被唤醒→重新加锁→再次判断条件→执行逻辑→解锁 + 唤醒。
相关推荐
Scott9999HH1 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
Yana.nice2 小时前
Linux 只保留 30 天内日志(find命令删除日志文件)
linux·运维·chrome
码智社2 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海2 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖3 小时前
JDK 26 新特性详解
java·开发语言
马优晨4 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
DFT计算杂谈6 小时前
无 Root 权限在 Tesla K80 零门槛部署 DeepSeek 大模型
linux·服务器·网络·数据库·机器学习
人邮异步社区6 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大6 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai6 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang