Linux线程同步

目录

[1 基本概念](#1 基本概念)

[2 条件变量](#2 条件变量)

[3 条件变量的核心操作](#3 条件变量的核心操作)

[3.1 条件变量初始化](#3.1 条件变量初始化)

[3.1.1 静态初始化(编译时初始化](#3.1.1 静态初始化(编译时初始化)

[3.1.2 动态初始化(运行时初始化)](#3.1.2 动态初始化(运行时初始化))

[3.1 ptread_cond_wait](#3.1 ptread_cond_wait)

[3.1.1 pthread_cond_wait 的工作原理](#3.1.1 pthread_cond_wait 的工作原理)

[3.1.2 常见问题](#3.1.2 常见问题)

[3.2 pthread_cond_signal](#3.2 pthread_cond_signal)

[3.2.1 基本工作原理](#3.2.1 基本工作原理)

[3.2.4 常见错误用法](#3.2.4 常见错误用法)

[3.3 pthread_cond_broadcast](#3.3 pthread_cond_broadcast)


1 基本概念


线程同步是指通过特定的机制,控制多个线程按照一定的顺序或规则访问共享资源,确保线程安全。
为什么需要线程同步:

  1. 数据一致性:防止多个线程同时修改共享数据导致数据损坏
  2. 避免竞态条件:确保操作的原子性
  3. 有序执行:控制线程执行的先后顺序,从而有效的防止线程饥饿

2 条件变量


条件变量(Condition Variable)是一种线程同步机制,用于让线程在某个条件不满足时进入等待状态,并在条件满足时被唤醒继续执行。它通常与互斥锁(Mutex)配合使用,以实现更复杂的线程同步逻辑。

为什么需要条件变量:

  1. 在多线程编程中,有时线程需要等待某个条件成立才能继续执行。如果仅使用互斥锁,线程可能需要不断轮询检查条件(忙等待),这会浪费CPU资源。
  2. 条件变量提供了一种高效等待机制,让线程在条件不满足时进入休眠状态,直到其他线程通知它条件可能已满足。

3 条件变量的核心操作


条件变量通常提供三个基本操作:

  1. wait(lock)
  • 线程调用 wait 时,会释放锁并进入等待状态,直到被唤醒。
  • 被唤醒后,它会重新获取锁,然后继续执行。
  • 必须配合互斥锁使用,以防止竞态条件。
  1. signal() / notify_one()
  • 唤醒一个正在等待该条件变量的线程(如果有多个线程在等待,只唤醒其中一个)。
  1. broadcast() / notify_all()
  • 唤醒所有正在等待该条件变量的线程。

3.1 条件变量初始化

3.1.1 静态初始化(编译时初始化

cpp 复制代码
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 使用宏定义初始化
  • 总是使用默认属性
  • 线程安全,不需要显式销毁

3.1.2 动态初始化(运行时初始化)

cpp 复制代码
#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
  • cond: 指向要初始化的条件变量的指针
  • attr : 指向条件变量属性对象的指针,通常设为 NULL 使用默认属性
  • 返回值 :
    • 成功返回 0
    • 失败返回错误码(如 EAGAIN 表示资源不足,ENOMEM 表示内存不足)
cpp 复制代码
pthread_cond_t cond;
// 初始化
pthread_cond_init(&cond, NULL);

// 销毁
pthread_cond_destory(&cond);
  • 更灵活,可以指定属性
  • 需要配合 pthread_cond_destroy 释放资源
  • 可以在运行时决定初始化时机

3.1 ptread_cond_wait

pthread_cond_wait 是 POSIX 线程(pthread)库中用于条件变量等待 的核心函数,它允许线程在某个条件不满足时挂起(阻塞) ,并在条件可能满足时被唤醒。它通常与互斥锁(pthread_mutex_t)配合使用,以实现线程间的同步。

cpp 复制代码
#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  • cond : 指向条件变量的指针(pthread_cond_t)。
  • mutex : 指向互斥锁的指针(pthread_mutex_t ),调用 pthread_cond_wait 前必须已加锁。
  • 返回值
    • 成功返回 0
    • 失败返回错误码(如 EINVAL 表示参数无效)

3.1.1 pthread_cond_wait 的工作原理

(1) 调用时发生了什么?

  1. 原子地释放 mutex(传递进来的锁资源),并让当前线程进入等待状态(阻塞)。
  2. 线程被移入条件变量的等待队列 ,直到被 pthread_cond_signalpthread_cond_broadcast 唤醒。
  3. 被唤醒后,需要重新获取(争夺锁资源) mutex(可能阻塞,直到锁可用),直到争夺到锁资源,然后继续执行。
    (2) 为什么需要 mutex
  • 防止竞态条件 :检查条件和进入等待必须是原子操作 ,否则可能出现:
    • 线程A检查条件(如 queue.empty() ),发现 true ,准备进入 wait
    • 线程B在A进入 wait 前修改了条件(如 queue.push() )并发出 signal
    • 线程A进入 wait ,但信号已经丢失,导致永久等待
  • pthread_cond_wait 内部会先解锁 mutex ,再进入等待 ,确保 signal 不会丢失。

3.1.2 常见问题

(1) 虚假唤醒(Spurious Wakeup)

  • 线程可能在没有收到 signal 的情况下被唤醒(由于系统优化或信号中断)。
  • 解决方案 :始终在 while 循环中检查条件
cpp 复制代码
while (!condition) {
    pthread_cond_wait(&cond, &mutex);
}

(2) 死锁风险

  • 忘记解锁 :如果 pthread_cond_wait 后忘记解锁,其他线程无法获取锁。
  • signal 丢失 :如果 signalwait 前发出,可能导致线程永久等待。
  • 解决方案 :确保 signal 发生在 wait 之后(或使用 pthread_cond_broadcast)。
    (3) pthread_cond_wait pthread_mutex_unlock 的顺序
  • 错误用法
cpp 复制代码
pthread_mutex_unlock(&mutex);
pthread_cond_wait(&cond, &mutex); // 未持有锁时调用,行为未定义!
  • 正确用法
cpp 复制代码
pthread_mutex_lock(&mutex);
pthread_cond_wait(&cond, &mutex); // 内部会先解锁,再等待
pthread_mutex_unlock(&mutex);

3.2 pthread_cond_signal

pthread_cond_signal 是 POSIX 线程(pthread)库中用于唤醒等待在条件变量上的线程 的关键函数。它通常与 pthread_cond_wait配合使用,实现线程间的同步通信。

cpp 复制代码
#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);
  • cond: 指向要发送信号的条件变量的指针
  • 返回值 :
    • 成功返回 0
    • 失败返回错误码(如 EINVAL 表示无效的条件变量)

3.2.1 基本工作原理

pthread_cond_signal 的主要作用是:

  1. 唤醒至少一个 正在 pthread_cond_wait 的线程(如果有多个线程在等待,具体唤醒哪个取决于实现)
  2. 如果没有线程在等待,这个信号会被丢弃(不会累积)
    关键特性:
  • 非阻塞 :调用 pthread_cond_signal 不会阻塞当前线程
  • 不保证立即执行:被唤醒的线程需要重新获取互斥锁后才能继续执行
  • 无记忆性 :如果调用 signal 时没有线程在等待,信号不会保存供后续使用
    关键注意事项:
  • 调用时通常应持有锁
  1. 最佳实践是在持有互斥锁时调用 signal
  2. 这样可以避免信号丢失(在检查和等待之间的竞争条件)
  • 信号可能丢失
  1. 如果在没有线程等待时调用 signal,信号会被丢弃
  2. 这通常不是问题,因为正确的模式应该使用条件变量+谓词检查
  • 虚假唤醒仍然可能发生
  1. 即使使用 signal ,被唤醒的线程仍应该检查条件(使用 while 而不是 if
  • 性能考虑
  1. signalbroadcast 更轻量
  2. 在只需要唤醒一个线程的情况下优先使用 signal
    底层实现机制:
    pthread_cond_signal 的具体实现依赖于操作系统,但通常涉及:
  3. 检查条件变量的等待队列
  4. 从队列中移出一个线程(FIFO或优先级顺序取决于实现)
  5. 将该线程标记为可运行状态
    在Linux中,这通常通过futex(快速用户空间互斥锁)机制实现,避免了不必要的内核切换。

3.2.4 常见错误用法

(1)错误1:不加锁调用

cpp 复制代码
// 错误!可能导致竞争条件
pthread_cond_signal(&cond);

应该:

cpp 复制代码
pthread_mutex_lock(&mutex);
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

(2)错误2:使用if而不是while检查条件

cpp 复制代码
// 错误!可能因虚假唤醒导致问题
if (!condition) {
    pthread_cond_wait(&cond, &mutex);
}

应该:

cpp 复制代码
while (!condition) {
    pthread_cond_wait(&cond, &mutex);
}

3.3 pthread_cond_broadcast

|-------|---------------------|------------------------|
| 特性 | pthread_cond_signal | pthread_cond_broadcast |
| 唤醒线程数 | 至少一个 | 所有等待线程 |
| 适用场景 | 单消费者/任意一个线程处理即可 | 多消费者/所有线程都需要处理 |
| 性能影响 | 较低(只唤醒一个) | 较高(唤醒所有) |
| 使用频率 | 更常用 | 特定场景使用 |

相关推荐
羑悻的小杀马特17 分钟前
把随身WiFi改成网盘聚合器:中兴F50挂载本地存储+夸克网盘实战
运维·服务器·人工智能·网盘·openlist
无人生还别怕25 分钟前
如何制作一个linux标准iso镜像
linux·iso·opencloudos·openanolisos
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
Yana.nice8 小时前
Linux 只保留 30 天内日志(find命令删除日志文件)
linux·运维·chrome
码智社9 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海9 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨10 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
吳所畏惧12 小时前
宝塔面板Redis密码修改指南:SSH命令修改 vs 面板UI界面修改,哪个更靠谱?
运维·服务器·数据库·redis·缓存·ssh
DFT计算杂谈12 小时前
无 Root 权限在 Tesla K80 零门槛部署 DeepSeek 大模型
linux·服务器·网络·数据库·机器学习