PV操作是操作系统中的进程同步机制,由荷兰计算机科学家Dijkstra提出,用于解决多进程/线程的互斥与同步问题。

  • P操作wait):申请资源,若资源不足则阻塞进程。
  • V操作signal):释放资源,唤醒等待进程。

1. PV操作核心原理

(1) 信号量(Semaphore)

  • 整型变量 S,表示可用资源数量。
  • 两个原子操作(不可中断):
  • P(S)S--,如果 S < 0,则进程阻塞。
  • V(S)S++,如果 S ≤ 0,则唤醒一个阻塞进程。

(2) 信号量类型

类型

初始值

用途

互斥信号量

1

实现临界区互斥访问

同步信号量

0

控制进程执行顺序(如生产者-消费者)


2. PV操作经典问题

(1) 互斥访问临界资源

semaphore mutex = 1; // 互斥信号量

Process P_i() {
    P(mutex);   // 进入临界区
    // 访问临界资源
    V(mutex);   // 离开临界区
}

特点

  • 同一时间仅允许一个进程进入临界区。
  • P和V必须成对出现,否则会导致死锁或资源泄漏。

(2) 生产者-消费者问题

semaphore empty = N;  // 缓冲区空槽数
semaphore full = 0;   // 缓冲区数据数
semaphore mutex = 1;  // 互斥锁

Producer() {
    while (true) {
        生产数据;
        P(empty);  // 申请空槽
        P(mutex);  // 申请缓冲区访问权
        放入缓冲区;
        V(mutex);  // 释放缓冲区
        V(full);   // 增加数据计数
    }
}

Consumer() {
    while (true) {
        P(full);   // 申请数据
        P(mutex);  // 申请缓冲区访问权
        取出数据;
        V(mutex);  // 释放缓冲区
        V(empty); // 增加空槽计数
        消费数据;
    }
}

关键点

  • 缓冲区访问必须互斥mutex)。
  • 必须先检查资源再互斥P(empty)P(mutex)之前),否则可能死锁。

(3) 读者-写者问题

semaphore rw_mutex = 1;  // 读写互斥
semaphore count_mutex = 1; // 读者计数互斥
int reader_count = 0;     // 当前读者数

Writer() {
    P(rw_mutex);     // 申请写权限
    // 写数据...
    V(rw_mutex);     // 释放写权限
}

Reader() {
    P(count_mutex);  // 保护reader_count
    reader_count++;
    if (reader_count == 1) {
        P(rw_mutex); // 第一个读者阻塞写者
    }
    V(count_mutex);
    
    // 读数据...
    
    P(count_mutex);
    reader_count--;
    if (reader_count == 0) {
        V(rw_mutex); // 最后一个读者释放写权限
    }
    V(count_mutex);
}

特点

  • 读者优先:只要有一个读者在读,写者就必须等待。
  • 可扩展为写者优先公平竞争版本。

3. PV操作常见考题

(1) 问:PV操作能否解决死锁?

✅ 可以预防死锁,但需满足:

  • 资源有序申请(避免循环等待)。
  • 超时机制(避免无限等待)。

(2) 问:如果P操作顺序错误会发生什么?

❌ 死锁!例如:

// 错误写法(可能导致死锁)
Producer() {
    P(mutex);  // 先申请互斥锁
    P(empty);  // 再申请空槽(如果empty=0,阻塞且不释放mutex)
    // ...
}

正确顺序:先申请资源信号量(empty/full),再申请互斥锁(mutex)。


4. 记忆口诀

  • P操作:“申请资源,不够就等。”
  • V操作:“释放资源,唤醒别人。”
  • 互斥锁:“初值1,P进V出。”
  • 同步信号量:“初值0,P等V放。”

5. 典型例子

题目
某系统有3个进程P1、P2、P3,共享一个缓冲区,P1生产数据,P2加工数据,P3消费数据。用PV操作实现同步。

答案

semaphore empty = 1;  // 缓冲区是否空
semaphore processed = 0; // 数据是否加工
semaphore consumed = 0;  // 数据是否消费

P1() { // 生产者
    while (true) {
        生产数据;
        P(empty);
        放入缓冲区;
        V(processed); // 通知P2
    }
}

P2() { // 加工者
    while (true) {
        P(processed);
        加工数据;
        V(consumed); // 通知P3
    }
}

P3() { // 消费者
    while (true) {
        P(consumed);
        取出数据;
        V(empty); // 通知P1
        消费数据;
    }
}