信号量(Semaphore)是操作系统中用于解决进程同步与互斥问题的一种经典机制,由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)于1965年提出。它是一个整型变量,仅支持两种原子操作:
- P操作(wait / down):将信号量值减1;若结果小于0,则调用进程阻塞并加入等待队列;
- V操作(signal / up):将信号量值加1;若存在等待进程,则唤醒其中一个。
信号量分为两类:
- 二元信号量(Binary Semaphore):取值仅为0或1,等价于互斥锁(Mutex),常用于实现临界区互斥;
- 计数信号量(Counting Semaphore):取值为非负整数,用于控制对有限资源(如缓冲区、I/O设备)的访问数量。
信号量的核心价值在于其原子性保障(通常由硬件指令如test-and-set或compare-and-swap实现),避免竞态条件,是构建更高级同步原语(如管程、条件变量)的基础。
// 简化示意:不可直接用于实际并发环境(需底层原子支持)
typedef struct {
int value;
queue_t *wait_queue; // 等待该信号量的进程队列
} semaphore_t;
void P(semaphore_t *s) {
s->value--;
if (s->value < 0) {
block_current_process(); // 将当前进程加入s->wait_queue并阻塞
add_to_wait_queue(s);
}
}
void V(semaphore_t *s) {
s->value++;
if (s->value <= 0) {
wakeup_one_from_queue(s); // 唤醒一个等待进程
}
}
P/V操作必须是原子的,是因为它们本质上是对共享变量(信号量值)的读-改-写(read-modify-write)操作,且其逻辑正确性严格依赖“不可分割执行”。若该操作被中断或与其他进程/线程的操作交错执行,将破坏信号量的语义,导致同步机制失效。
具体问题举例(以非原子P操作为例):
假设信号量 s.value = 1,两个进程 P₁ 和 P₂ 同时执行 P(s):
非原子P操作分解为三步(非原子执行):
- 读取
s.value→ 得到 1 - 计算
s.value - 1 = 0 - 写回
s.value = 0
若执行顺序交错如下(时间轴从上到下):
| 时间 | P₁ | P₂ |
|---|---|---|
| t₁ | 读 s.value=1 | |
| t₂ | 读 s.value=1 | |
| t₃ | 计算得 0 | |
| t₄ | 计算得 0 | |
| t₅ | 写 s.value=0 | |
| t₆ | 写 s.value=0 |
→ 最终 s.value = 0(正确值应为 -1,因两次P后应阻塞一个进程),但两个进程都通过了P操作,均进入临界区!
→ 违反互斥原则:本应只允许一个进程进入,却出现并发访问——典型竞态条件(race condition),可能导致数据不一致、状态错乱甚至系统崩溃。
类似地,非原子V操作可能造成:
- 唤醒丢失(如多个V同时执行,仅一个生效,其余唤醒被覆盖);
- 等待队列管理错误(如未正确检查是否有等待者就跳过唤醒)。
✅ 原子性保障方式:
- 硬件支持:
test-and-set、compare-and-swap (CAS)、load-linked/store-conditional (LL/SC)等指令; - 操作系统内核中常结合关中断(单核)或自旋锁+原子指令(多核)实现。

2434

被折叠的 条评论
为什么被折叠?



