> For the complete documentation index, see [llms.txt](https://aye10032.gitbook.io/os/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://aye10032.gitbook.io/os/di-er-zhang-jin-cheng-guan-li/2.3-jin-cheng-guan-li-jing-dian-jin-cheng-tong-bu-wen-ti.md).

# 2.3 进程管理（经典进程同步问题）

{% hint style="info" %}
互斥：

* P
* 临界资源
* V

同步：

* 前置事件
* V
*
* P
* 后置事件
  {% endhint %}

## 一、生产者-消费者问题

* 系统中有一组生产者进程和一组消费者进程，生产者进程每次生产一个产品放入缓冲区，消费者进程每次从缓冲区中取出一个产品并使用；
* 生产者、消费者共享一个初始为空、大小为n的缓冲区；
* 只有缓冲区**没满**时，生产者才能把产品**放入**缓冲区，否则必须等待；
* 只有缓冲区**不空**时，消费者才能从中**取出**产品，否则必须等待；
* 缓冲区是临界资源，各进程必须**互斥地**访问。

### 1、分析关系

* 生产者和消费者访问缓冲区是**互斥**的；
* 生产者和消费者的工作需要**同步**，即生产完成之后才能消费。

![生产者-消费者同步关系](/files/-Mfz8pZI6Lm6dzhYrMeg)

### 2、设置信号量

```c
semaphore mutex = 1;    //互斥信号量，实现对缓冲区的互斥访问
semaphore empty = n;    //同步信号量，表示空闲缓冲区的数量
semaphore full = 0;     //同步信号量，表示产品（非空缓冲区）的数量
```

### 3、实际代码

```c
producer(){
    while(1){
        生产产品
        P(empty);    //申请新的缓冲区
        P(mutex);    //申请访问临界资源
        存入缓冲区
        V(mutex);    //释放临界资源
        V(full);     //释放一个消费品（非空缓冲区）
    }
}

consumer(){
    while(1){
        P(full);     //申请使用消费品
        P(mutex);    //申请访问临界资源
        从缓冲区取出
        V(mutex);    //释放临界资源
        V(empty);    //释放一个空缓冲区
        消费产品
    }
}
```

{% hint style="danger" %}
实现**互斥**的P操作一定要在实现**同步**的P操作**之后**

两个V操作可以交换顺序
{% endhint %}

## 二、多生产者-多消费者问题

有父亲、母亲、儿子、女儿四人，其中：

* 父亲往盘子中放苹果；
* 母亲往盘子中放橘子；
* 女儿从盘子中拿苹果；
* 儿子从盘子中拿橘子；
* 只有盘子空时才能放水果；
* 只有有需要的水果时才能拿水果。

### 1、分析关系

* 互斥
  * 对盘子的访问是互斥的
* 同步
  * 父亲放入苹果后，女儿才能拿苹果
  * 母亲放入橘子后，儿子才能拿橘子
  * 盘子为空时才能放水果

![](/files/-MfzF4CT6c22udciRc6Z)

### 2、设置信号量

```c
semaphore mutex = 1;    //互斥信号量，实现盘子的互斥访问
semaphore plate = 1;    //同步信号量，代表盘子的剩余空位
semaphore apple = 0;    //同步信号量，代表苹果数量
semaphore orange = 0;   //同步信号量，代表橘子数量
```

### 3、实际代码

```c
dad(){
    while(1){
        准备苹果
        P(plate);        //申请盘子资源
        P(mutex);
        将苹果放入盘子
        V(mutex);
        V(apple);        //释放一个苹果
    }
}

mom(){
    while(1){
        准备橘子
        P(plate);        //申请盘子资源
        P(mutex);
        将橘子放入盘子
        V(mutex);
        V(orange);       //释放一个橘子
    }
}

daughter(){
    while(1){
        P(apple);        //申请苹果资源
        P(mutex);
        拿出苹果
        V(mutex);
        V(plate);        //释放盘子资源
        恰苹果        
    }
}

son(){
    while(1){
        P(orange);       //申请橘子资源
        P(mutex);
        拿出橘子
        V(mutex);
        V(plate);        //释放盘子资源
        恰橘子        
    }
}
```

由于本问题缓冲区为1，可以考虑不设置信号量。

## 三、吸烟者问题

系统中有一个供应者和三个吸烟者，吸烟者吸烟需要自己卷烟，其中

* 卷烟需要烟草、纸、胶水三种材料
* 每个吸烟者各有其中的一种材料
* 供应者每次会提供其中两种材料，并由缺少该材料的吸烟者拿取
* 吸烟者制作完烟并抽掉后，发出信号，供应者放下一组物品

### 1、分析关系

可以将桌子视为容量为1的缓冲区，并且将两种材料分别视为三种组合：

* 组合一：纸+胶水
* 组合二：烟草+胶水
* 组合三：烟草+纸

![](/files/-Mg-fZh61EXkawaAfikU)

* 互斥
  * 对桌子的访问需要互斥进行
* 同步
  * 桌上有组合一，第一个抽烟者取走物品
  * 桌上有组合二，第二个抽烟者取走物品
  * 桌上有组合三，第三个抽烟者取走物品
  * 发出完成信号后，供应者将下一个组合放到桌上

### 2、信号量设置

```c
semaphore offer1 = 0;    //同步信号量，桌上组合一的数量
semaphore offer1 = 0;    //同步信号量，桌上组合二的数量
semaphore offer1 = 0;    //同步信号量，桌上组合三的数量
semaphore finish = 0;    //同步信号量，抽烟是否完成
int i = 0;               //实现轮流提供材料
```

### 3、实际代码

```c
provider(){
    while(1){
        if (i==0){
            将组合一放在桌上
            V(offer1);        //提供材料
        }else if (i==1){
            将组合二放在桌上
            V(offer2);
        }else if (i ==2){
            将组合三放在桌上
            V(offer3);
        }
        i = (i+1) % 3;       //轮流提供素材
        P(finish);           //等待完成信号
    }
}

smoker1(){
    while(1){
        P(offer1);           //请求组合一资源
        从桌上拿走组合一
        卷烟，抽烟
        V(finish);           //发出完成信号
    }
}

smoker2(){
    while(1){
        P(offer2);
        从桌上拿走组合二
        卷烟，抽烟
        V(finish);
    }
}

smoker3(){
    while(1){
        P(offer3);
        从桌上拿走组合三
        卷烟，抽烟
        V(finish);
    }
}
```

## 四、读者-写者问题

有读者和写者两组并发进程，共享一个文件。要求：

* 读者可以同时读取文件；
* 同一时间只能有一个写者进行写操作；
* 任一写着完成写操作之前不允许其他进程进行读或写操作；
* 写者执行写操作前，应让其他读者和写者全部退出。

### 1、关系分析

* 互斥
  * 写进程和写进程之间
  * 写进程和读进程之间

### 2、信号量设置

```c
semaphore rw = 1;        //互斥信号量，实现读、写对文件的互斥访问
int count = 0;           //同时在读文件的读进程数量
semaphore mutex = 1;     //互斥信号量，实现读进程对count的互斥访问
```

然而，以上信号量设置有可能导致**饿死**，具体如下代码一所示，因此，增加一个信号量

```c
semaphore w = 1;          //同步信号量，用于实现“写优先”
```

### 3、实际代码

```c
writer(){
    while(1){
        P(rw);
        写文件
        V(rw);
    }
}

reader(){
    while(1){
        P(mutex);
        if (count == 0){
            P(rw);        //由第一个读进程负责上锁
        }
        V(mutex);
        
        读文件
        
        P(mutex);
        count--;
        if (count == 0){
            V(rw);        //由最后一个读进程负责解锁
        }
        V(mutex);
    }
}
```

以上代码存在饿死现象，即一直有读进程占用，写进程始终无法运行

因此，引入了“写优先”的信号量：

```c
writer(){
    while(1){
        P(w);
        P(rw);
        写文件
        V(rw);
        V(w);
    }
}

reader(){
    while(1){
        P(w);             //对每一个读和写进程之间做互斥处理
        P(mutex);
        if (count == 0){
            P(rw);        //由第一个读进程负责上锁
        }
        V(mutex);
        V(w);
        
        读文件
        
        P(mutex);
        count--;
        if (count == 0){
            V(rw);        //由最后一个读进程负责解锁
        }
        V(mutex);
    }
}
```

当然，这种实际上是各个读、写进程之间公平运行，并不是准确的写优先。

## 五、哲学家进餐问题

在一个桌子上，有5位哲学家，其中

* 每位哲学家之间摆着一**根**筷子，共计5根
* 哲学家平时在思考人生，饿了就会尝试拿起左右手的筷子（一根一根的拿）
* 如果筷子在别人手上，则等待
* 拿起了两根筷子时，哲学家开始进食
* 用餐完毕后放下筷子继续思考

### 1、关系分析

设计有三种思路来防止死锁：

1. 将拿左右筷子的动作整体设为互斥操作
2. 最多只允许四个人拿筷子
3. 只允许**奇数号**的哲学家拿起**左边**的筷子；**偶数号**的哲学家拿起**右边**的筷子

### 2、信号量设置

```c
semaphore chopstick[5] = {1, 1, 1, 1, 1};    //代表五根筷子的资源
semaphore mutex = 1;                         //对左右筷子整体资源互斥访问
```

### 3、实际代码

```c
Pi(){
    while (1){
        P(mutex);
        P(chopstick[i]);            //拿左
        P(chopstick[ (i+1) % 5]);   //拿右
        V(mutex);
        
        吃饭
        
        V(chopstick[i]);
        V(chopstick[ (i+1) % 5]);
        
        思考
    }
}
```

实现了只有当左右手筷子都可用时才拿起筷子


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://aye10032.gitbook.io/os/di-er-zhang-jin-cheng-guan-li/2.3-jin-cheng-guan-li-jing-dian-jin-cheng-tong-bu-wen-ti.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
