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

相关推荐
Meaauf2 小时前
VMware五种网络模式详解与EVE-NG互联指南
运维·服务器·网络
桦02 小时前
[Linux复习]:网络
linux·运维·网络
神の愛2 小时前
java日志功能
java·开发语言·前端
Reuuse2 小时前
基于 C++ 的网页五子棋对战项目实战
开发语言·c++
不会写DN2 小时前
如何设计应用层 ACK 来补充 TCP 的不足?
开发语言·网络·数据库·网络协议·tcp/ip·golang
何中应2 小时前
Linux的systemctl命令
linux·运维·服务器
xyq20242 小时前
PHP MySQL 简介
开发语言
我能坚持多久2 小时前
利用Date类的实现对知识巩固与自省
开发语言·c++
IMPYLH2 小时前
Linux 的 mktemp 命令
linux·运维·服务器·bash