信号量、条件量
POSIX信号量
基本概念
POSIX信号量与IPC信号量组的核心逻辑一致,但操作更简便、接口更易用,是多进程/多线程并发编程中的常用同步工具。
信号量分类
- POSIX匿名信号量
- 适用场景:线程间同步
- 存储特性:仅存在于内存,文件系统中不可见
- POSIX具名信号量
- 适用场景:进程间同步
- 存储特性:存在于文件系统
/dev/shm目录,可被任意有权限的进程访问
核心:PV操作
对于信号量而言,最重要的作用就是用来表征对应资源的数量,所谓的P/V操作就是对资源数量进行 +1/-1 操作,既然只是个加减法,那么为什么不使用普通的整型数据呢?原因是:
- 整型数据的加减操作不具有原子性,即操作可能被中断
- 普通加减法无法提供阻塞特性,而申请资源不可得时应进入阻塞
对于原子性再做个简单的解释,即这种资源数量的加减法不能有中间过程,不管是成功还是失败都必须一次性完成。加减法看似简单,但在硬件层面上并非一个原子性操作,而是包含了多个寄存器操作步骤,因此不能作为P/V操作的手段。
1.P操作是申请资源,因此如果资源数不够的话会导致进程阻塞,这正是我们想要的效果,因为资源数不可为负数。
2.V操作是释放资源,永远不会阻塞。
3.SEM对象的一大特色就是可以对多个信号量元素同时进行P/V操作,这也是跟POSIX单个信号量的区别。
如果需要使用信号量来实现进程间的同步操作,则可以使用PV来实现,比如A进程运行成功后会释放一个信号量s1 ,二进程B 则在运行前需要得到信号量 s1 ,这么一来不管AB两个进程谁先运行,我们都可以确定B进行在进行某项操作前必须得到A进程的信号。
为何不用普通整型
- 普通整型加减不具备原子性,硬件层面需多步寄存器操作,可能被中断
- 普通加减法无阻塞特性,资源申请失败时无法让进程/线程进入等待状态
PV操作定义
- P操作(申请资源):资源数减1,若资源不足则阻塞,确保资源数不为负
- V操作(释放资源):资源数加1,永远不会阻塞
- 补充区别:IPC信号量组可对多个信号量元素同时执行PV操作,POSIX信号量仅支持单个操作
PV操作的同步作用
通过PV操作可实现进程/线程间的顺序控制。例如:
- 进程A执行成功后,通过V操作释放信号量s1
- 进程B执行前需通过P操作获取信号量s1
- 无论A、B启动顺序如何,都能保证B的目标操作在A之后执行
匿名信号量操作流程
- 定义信号量:可存储在栈、堆或数据段,确保多个任务能便捷访问
- 初始化信号量
- 执行PV操作
- 销毁信号量
匿名信号量核心接口
初始化与销毁
#include <semaphore.h>
int sem_destroy(sem_t *sem); // 销毁信号量
int sem_init(sem_t *sem, int pshared, unsigned int value); // 初始化信号量
参数说明:
- sem:待初始化/销毁的目标信号量指针
- pshared:是否支持进程间使用(0=线程间,非0=进程间,需确保信号量可被多进程直接访问)
- value:信号量初始值,表征初始资源数量
V操作(释放资源)
#include <semaphore.h>
int sem_post(sem_t *sem); // 资源数+1,无阻塞
P操作(申请资源)
#include <semaphore.h>
int sem_wait(sem_t *sem); // 阻塞式申请,资源不足时休眠
int sem_trywait(sem_t *sem); // 非阻塞式申请,资源不足时直接返回错误
int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict abs_timeout); // 限时阻塞申请
具名信号量
核心特性
- 适用场景:多进程间同步互斥
- PV操作逻辑:与匿名信号量完全一致
- 关键区别:存在于
/dev/shm文件系统,可被任意有权限的进程打开访问
创建与打开接口
#include <fcntl.h> /* 包含O_*常量定义 */
#include <sys/stat.h> /* 包含mode常量定义 */
#include <semaphore.h>
// 两种版本,类似文件open()函数
sem_t *sem_open(const char *name, int oflag); // 后续打开已创建的信号量
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); // 首次创建时使用
参数说明:
- name:信号量名称(文件系统中可见的标识)
- oflag:打开标志(如O_CREAT表示创建)
- mode:文件权限(首次创建时必须指定)
- value:信号量初始值(首次创建时必须指定)
关闭与删除接口
#include <semaphore.h>
int sem_close(sem_t *sem); // 关闭信号量,释放内核维护的资源
int sem_unlink(const char *name); // 删除信号量对应的文件,彻底清理资源
注意事项
- 具名信号量关闭后,文件不会自动消失,其值会保留所有PV操作结果
- 例:连续执行3次释放操作(V),信号量值会+3,后续可执行3次申请操作(P)
- 即使所有进程退出,信号量文件仍存在,需通过
sem_unlink()或文件系统手动删除
拓展知识:信号量常见应用场景
- 资源池管理:限制同时访问某资源的进程/线程数量(如数据库连接池)
- 生产者-消费者模型:平衡生产和消费速度,避免缓冲区溢出或空读
- 临界区保护:确保同一时间只有一个进程/线程访问临界资源(二值信号量)
条件量
基本概念
在许多场合中,程序的执行通常需要满足一定的条件,条件不成熟的时候,任务应该进入睡眠阻塞等待,条件成熟时应该可以被快速唤醒(异步)。另外,在并发程序中,会与其他任务同时访问该条件,因此任何时候都必须以互斥的方式对条件进行访问。条件量就是专门解决上述场景的逻辑机制。
注意,上述表述中,条件和条件量是两个不同的东西,所谓条件就是指程序要继续运行所需要的前提条件,比如文件是否读完、内存是否清空等具体的场景限定,而条件量(即pthread_cond_t)是本节课件要讨论的一种同步互斥变量,专用于解决上述逻辑场景。
关键术语区分
- 条件:程序继续执行的前提(如文件读完、内存清空等具体场景)
- 条件量:
pthread_cond_t类型变量,提供阻塞睡眠和唤醒功能
核心执行逻辑
说明:
1.在进行条件判断前(判断的目标一般是临界资源),先加锁(防止其他任务并发访问)
2.成功加锁后,判断条件是否允许
- 若条件允许,则直接操作临界资源,然后释放锁
- 若条件不允许,则进入条件量的等待队列中睡眠,并同时释放锁
3.在条件量中睡眠的任务,可以被其他任务唤醒,唤醒时重新判定条件是否允许程序继续执行,当然也是必须先加锁。
核心特性
- 必须与互斥锁(或二值信号量)配套使用
- 互斥锁负责临界资源的互斥访问,条件量负责阻塞和唤醒
- 支持异步唤醒,条件成熟时可快速响应
操作流程与核心接口
定义与初始化
#include <pthread.h>
// 动态初始化
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
// 动态销毁
int pthread_cond_destroy(pthread_cond_t *cond);
// 静态初始化(更简洁)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
参数说明:
- cond:待初始化/销毁的条件量指针
- attr:条件量属性,NULL表示使用默认属性
阻塞等待接口
#include <pthread.h>
// 无限期阻塞等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
// 限时阻塞等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
参数说明:
- cond:目标条件量,条件不满足时进入等待队列
- mutex:需释放的互斥锁,唤醒时自动重新获取
- abstime:等待的绝对时间(超时后自动返回)
唤醒接口
#include <pthread.h>
// 广播唤醒:唤醒所有等待该条件量的任务
int pthread_cond_broadcast(pthread_cond_t *cond);
// 单播唤醒:唤醒任意一个等待该条件量的任务
int pthread_cond_signal(pthread_cond_t *cond);
拓展:条件量常见错误与避免
- 错误1:未加锁直接判断条件 → 避免:始终在互斥锁保护下判断条件
- 错误2:持有互斥锁进入等待 → 避免:
pthread_cond_wait()会自动释放锁,无需手动解锁 - 错误3:唤醒后未重新判断条件 → 避免:唤醒后可能条件已变化,需再次检查
- 错误4:使用
pthread_cond_signal()唤醒多个等待任务 → 避免:需唤醒多个时用pthread_cond_broadcast()
信号量与条件量对比
| 特性 | 信号量 | 条件量 |
|---|---|---|
| 核心作用 | 管理资源数量,实现同步互斥 | 基于条件的阻塞/唤醒,实现同步 |
| 依赖组件 | 无需额外依赖(二值信号量可模拟锁) | 必须与互斥锁配套使用 |
| 适用场景 | 资源计数、临界区保护、进程/线程同步 | 条件触发型场景(如生产者-消费者、等待事件) |
| 阻塞触发条件 | 资源不足 | 条件不满足 |
| 唤醒方式 | 释放资源(V操作)自动唤醒 | 需显式调用signal/broadcast接口 |

浙公网安备 33010602011771号