信号量实现进程同步与互斥详解
信号量是一种用于实现进程间同步与互斥的经典机制,由操作系统提供,通过 P(wait)和 V(signal)两个原子操作来管理。它本质上是一个整型变量(或记录型变量),记录了可用资源的数量,并关联一个等待队列,用于阻塞暂时无法获取资源的进程。valuequeue是信号量机制的核心,它们必须是(执行过程不可被中断)。注:上述代码逻辑展示了信号量操作的核心思想。在实际操作系统中,这些操作由内核以系统调用
信号量是一种用于实现进程间同步与互斥的经典机制,由操作系统提供,通过 P(wait)和 V(signal)两个原子操作来管理。它本质上是一个整型变量(或记录型变量),记录了可用资源的数量,并关联一个等待队列,用于阻塞暂时无法获取资源的进程 。
一、信号量的基本结构与操作
信号量通常分为两种类型:
- 整型信号量:仅有一个整数值,用于表示资源数量,但不记录等待进程,可能导致“忙等” 。
- 记录型信号量:包含一个整型值
value和一个进程等待队列queue,是更常用的实现方式,能有效避免忙等,遵循“让权等待”原则 。
记录型信号量的数据结构可抽象表示如下:
typedef struct {
int value; // 当前信号量的值,通常表示可用资源数
struct process *queue; // 指向因等待该信号量而阻塞的进程队列
} semaphore;
P(wait)操作和 V(signal)操作是信号量机制的核心,它们必须是原子操作(执行过程不可被中断)。
// P操作(等待、申请资源)
void P(semaphore *S) {
S->value--; // 表示申请一个资源
if (S->value < 0) {
// 资源不足,将当前进程插入S->queue阻塞队列
block(S->queue); // 该进程自我阻塞,放弃CPU
}
}
// V操作(发信号、释放资源)
void V(semaphore *S) {
S->value++; // 表示释放一个资源
if (S->value <= 0) {
// 有进程在等待,从S->queue中唤醒一个进程
wakeup(S->queue); // 唤醒一个等待进程,使其进入就绪态
}
}
注:上述代码逻辑展示了信号量操作的核心思想。在实际操作系统中,这些操作由内核以系统调用的形式提供,确保其原子性 。
二、使用信号量实现进程互斥
互斥用于确保临界资源(一个时间段内只允许一个进程使用的资源,如打印机、共享变量等)被安全访问 。其关键是设置一个初始值为 1 的互斥信号量 mutex,将其视为进入临界区的“钥匙”。
实现模型如下:
semaphore mutex = 1; // 初始化互斥信号量为1,表示钥匙可用
Process Pi() {
while(1) {
P(mutex); // 进入区:尝试获取钥匙,若被占用则阻塞
// 临界区 (Critical Section)
// 访问共享资源/临界资源的代码段
V(mutex); // 退出区:释放钥匙
// 剩余区 (Remainder Section)
}
}
原理说明:
- 任何进程在进入临界区前必须先执行
P(mutex)。 - 当
mutex=1,第一个进程执行P(mutex)后,mutex变为 0,该进程进入临界区。 - 此时若第二个进程也执行
P(mutex),mutex将变为 -1,该进程被阻塞并放入等待队列。 - 第一个进程退出临界区时执行
V(mutex),mutex从 0 变回 0(因value<=0),它会唤醒等待队列中的一个进程。 - 被唤醒的进程从
P(mutex)中恢复,完成value--(此时value可能仍为负,表示还有进程在等待),然后进入临界区。
这种方法严格保证了“忙则等待”(有进程在临界区时,其他进程阻塞)和“空闲让进”(临界区空闲时,允许一个进程立即进入)的互斥原则 。
三、使用信号量实现进程同步
同步用于协调合作进程之间的执行顺序,即某些操作必须在另一些操作之后执行(直接制约关系)。通常设置一个初始值为 0 的同步信号量 S。
经典案例:保证操作A在操作B之后执行
假设有两个并发进程 P1 和 P2,P1 中有一段代码 A,P2 中有一段代码 B。要求 A 执行完成后,B 才能开始执行。
semaphore syn = 0; // 初始化同步信号量为0
Process P1() {
// ...
// 执行代码A
printf("P1 executes operation A.
");
V(syn); // 操作A完成,发信号给P2
// ...
}
Process P2() {
// ...
P(syn); // 等待P1完成操作A的信号
// 执行代码B
printf("P2 executes operation B after A is done.
");
// ...
}
原理说明:
- 初始时
syn=0,表示“事件A已完成”这个信号尚未发生。 - 若 P2 先执行到
P(syn),由于syn--后变为 -1,P2 被阻塞。 - 当 P1 执行完 A 后,执行
V(syn),使syn从 -1 变为 0,并唤醒阻塞的 P2。 - P2 被唤醒后,继续执行 B。从而保证了
A→B的执行顺序。
四、综合应用:生产者-消费者问题
生产者-消费者问题是同步与互斥结合的经典模型。它包含:
- 一个大小为 n 的缓冲区(临界资源)。
- 两类进程:生产者(向缓冲区放产品)和消费者(从缓冲区取产品)。
- 三种关系:
- 生产者与生产者互斥访问缓冲区(放置产品)。
- 消费者与消费者互斥访问缓冲区(取走产品)。
- 生产者与消费者同步:缓冲区满时生产者等待;缓冲区空时消费者等待。
解决方案需要三个信号量:
semaphore mutex = 1; // 互斥信号量,用于互斥访问缓冲区(临界区)
semaphore empty = n; // 同步信号量,表示空闲缓冲区数量,初始为n
semaphore full = 0; // 同步信号量,表示已填充缓冲区数量,初始为0
生产者和消费者的伪代码如下:
// 生产者进程
Producer() {
while(1) {
produce an item; // 生产一个产品
P(empty); // 申请一个空缓冲区(若满则阻塞)[同步]
P(mutex); // 申请进入临界区(互斥访问缓冲区)[互斥]
// 临界区开始
put item into buffer; // 将产品放入缓冲区
// 临界区结束
V(mutex); // 离开临界区,释放锁
V(full); // 增加一个满缓冲区,通知消费者 [同步]
}
}
// 消费者进程
Consumer() {
while(1) {
P(full); // 申请一个满缓冲区(若空则阻塞)[同步]
P(mutex); // 申请进入临界区(互斥访问缓冲区)[互斥]
// 临界区开始
take item from buffer; // 从缓冲区取出产品
// 临界区结束
V(mutex); // 离开临界区,释放锁
V(empty); // 增加一个空缓冲区,通知生产者 [同步]
consume the item; // 消费产品
}
}
关键点与注意事项:
- 互斥信号量
mutex的初始值必须为 1,以实现对缓冲区的互斥访问 。 - 同步信号量
empty和full的初始值分别对应缓冲区的初始状态(全空和全满)。 - P操作的顺序至关重要。在上例中,必须先对同步信号量(
empty/full)执行 P 操作,再对互斥信号量(mutex)执行 P 操作。如果顺序颠倒(先P(mutex)再P(empty)),当缓冲区满时,生产者会先获得缓冲区的锁,然后发现没有空位而阻塞,同时它又持有锁,导致任何消费者都无法进入缓冲区取走产品,从而形成死锁 。 - V操作的顺序一般没有严格要求,因为它们不会导致进程阻塞。
五、信号量与其它互斥同步方法的对比
| 方法 | 核心思想 | 优点 | 缺点 | 是否满足“让权等待” |
|---|---|---|---|---|
| 软件方法 (如Peterson算法) | 通过共享变量和循环检查实现 | 纯软件实现,不依赖特殊硬件 | 实现复杂,可能违背“让权等待”(忙等) | 否 |
| 硬件方法 (如中断屏蔽、TSL/Swap指令) | 利用硬件指令的原子性实现上锁 | 简单高效,适用于多处理机 | 通常不满足“让权等待”(忙等) | 否 |
| 互斥锁 (Mutex Lock) | 提供acquire()和release()原子操作 | 概念简单,使用方便 | 通常基于硬件实现,也可能忙等 | 取决于具体实现 |
| 信号量机制 | 通过P/V原子操作和等待队列管理资源 | 功能强大,既可实现互斥,也可实现复杂的同步;记录型信号量满足“让权等待” | 使用不当易造成死锁、优先级反转等问题 | 记录型信号量是 |
总结来说,信号量机制是操作系统层面提供的强大工具。通过互斥信号量(初始值1) 可以解决对临界资源的独占访问问题;通过同步信号量(初始值常为0或资源数) 可以解决进程间的执行顺序协调问题。在实际编程中(如Linux下的System V IPC或POSIX信号量),其接口(如 sem_wait, sem_post)封装了底层的P/V操作,但核心逻辑与上述一致 。正确理解并应用信号量,是编写正确、高效并发程序的基础。
参考来源
- 操作系统(8)---进程的同步与互斥以及信号量机制(万字总结~)
- 操作系统---(22)信号量机制解决同步互斥问题
- 2.3进程的同步概念和经典同步互斥问题
- 【Linux 系统】进程间通信(共享内存、消息队列、信号量)
- 2.4 CPU管理--信号量和PV操作
- 计算机四级 - 数据库原理(操作系统部分)- 第4章「并发与同步」
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐


所有评论(0)