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(释放锁 + 阻塞)→被唤醒→重新加锁→再次判断条件→执行逻辑→解锁 + 唤醒。
相关推荐
爱写代码的liding1 小时前
linux安装软件过程中报找不到某些动态链接.so文件
linux
清水白石0081 小时前
《Python 编程全景解析:从核心精要到 Hypothesis 属性基测试的边界探索》
开发语言·python
IT枫斗者2 小时前
IntelliJ IDEA 2025.3史诗级更新:统一发行版+Spring Boot 4支持,这更新太香了!
java·开发语言·前端·javascript·spring boot·后端·intellij-idea
青衫码上行2 小时前
【项目部署】Spring Boot项目部署的四种方式
java·linux·服务器·spring boot·后端·docker·腾讯云
勇往直前plus2 小时前
深入理解 Python 内存模型:模块、类、对象的存储与运行机制
开发语言·python
派大星-?3 小时前
自动化测试五模块一框架(下)
开发语言·python
礼拜天没时间.3 小时前
JumpServer堡垒机部署与实战:从0到1搭建统一运维入口
linux·运维·架构·堡垒机·jumpserver·sre
林姜泽樾3 小时前
linux入门第四章,cd指令和相对、绝对路径
linux·运维·服务器