1
0
mirror of https://github.com/Didnelpsun/CS408.git synced 2026-06-16 23:17:21 +08:00

更新进程管理

This commit is contained in:
Didnelpsun
2021-08-16 23:12:37 +08:00
parent ed3fa673a6
commit 40bc3cd78c
2 changed files with 175 additions and 68 deletions

View File

@@ -344,3 +344,35 @@ $D.10ms$
解:$C$。$0ms$时刻$P1$开始运行。$10ms$时刻$P1$停止,余下$20ms$进入$Q2$$P2$进入$Q1$运行。$20ms$时刻$P2$停止,余下$10ms$,进入$Q2$,而$Q1$空,开始运行$Q2$中的进程,而$P2$余下$10ms$小于$P1$余下的$20ms$,短进程优先,所以$P2$继续开始运行。$30ms$时刻$P2$完成,$P1$运行。$50ms$时刻$P1$运行完成。
所以综上$P1$等待$30-10=20ms$,而$P2$等待$10-0=10ms$。平均等待时间为$(20+10)\div2=15ms$。
#### 多道批处理系统
一般都是单道处理系统,所以作业调度和进程调度都是一样的。而对于多道批处理系统会采用不同的作业调度和进程调度算法。但是基本上运算方法一致。
作业调度是调度作业进内存,有多少道就有多少个内存作业位置,但是有位置不一定能运行,因为需要进行进程调度占用处理机($CPU$)。
**例题** 有一个具有两道作业的批处理系统,作业调度采用短作业优先调度算法,进程调度采用抢占式优先级调度算法。作业的运行情况见下表,其中作业的优先数即进程的优先数,优先数越小,优先级越高。
作业名|到达时间|运行时间|优先数
:----:|:------:|:------:|:----:
1|8:00|40分钟|5
2|8:20|30分钟|3
3|8:30|50分钟|4
4|8:50|20分钟|6
1列出所有作业进入内存的时间及结束的时间以分为单位)。
2计算平均周转时间。
解:
1具有两道作业的批处理系统内存只存放两道作业它们采用抢占式优先级调度算法竞争$CPU$,而将作业调入内存采用的是短作业优先调度。$8:00$,作业$1$到来,此时内存和处理机空闲,作业$1$进入内存并占用处理机;$8:20$,作业$2$到来,内存仍有一个位置空闲,因此将作业$2$调入内存,又由于作业$2$的优先数高,相应的进程抢占处理机,在此期间$8:30$作业$3$到来,但内存此时已无空闲,因此等待。直至$8:50$,作业$2$执行完毕,此时作业$3$、$4$竞争空出的一道内存空间,作业$4$的运行时间短,因此先调入,但它的优先数低于作业$1$,因此作业$1$先执行。到$9:10$时,作业$1$执行完毕,再将作业$3$调入内存,且由于作业$3$的优先数高而占用$CPU$,作业$3$完成后$4$开始占用$CPU$处理。所有作业进入内存的时间及结束的时间见下表。
作业|到达时间|运行时间|优先数|进入内存时间|结束时间|周转时间
:--:|:------:|:------:|:----:|:---------:|----:|:----:
1|8:00|40min|5|8:00|9:10|70min
2|8:20|30min|3|8:20|8:50|30min
3|8:30|50min|4|9:10|10:00|90min
4|8:50|20min|6|8:50|10:20|90min
2平均周转时间$为(70+30+90+90)\div4=70min$。

View File

@@ -393,7 +393,6 @@
适用于|无|作业调度,批处理系统|无|分时系统|通用
默认决策模式|非抢占|非抢占|非抢占|抢占|抢占
#### 算法评价指标
+ $CPU$利用率:$CPU$忙碌时间占总时间的比例。其中利用率=$CPU$忙碌(运行)时间÷进程运行总时间。
@@ -576,46 +575,56 @@
### 进程互斥的软件实现
即在进入区设置并检查一些标志来表明是否有进程在临界区,若有则在进入区通过循环检查进行等待,进程离开临界区后则在退出区修改标志。
#### 单标志法
算法思想:两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予。
算法思想:两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予。所以设置一个公用整型变量`turn`用来表示允许进入临界区的进程编号,若`turn=0`则允许$P_0$进入临界区。
```cpp
// turn表示当前允许进入临界区的进程号
int turn = 0;
// P0进程
// 进入区
while (turn != 1); // ①
// 临界区
critical section; // ②
// 退出区
turn = 1; // ③
// 剩余区
remainder section; // ④
// P1进程
// 进入区
while(turn != 0); // ⑤
// 临界区
critical section; // ⑥
// 退出区
turn = 0; // ⑦
// 剩余区
remainder section;// ⑧
```
优点:
+ turn的初值为0,即刚开始只允许0号进程进入临界区。
+ 若P1先上处理机运行,则会一直卡在⑤,无法使用临界资源。
+ 直到P1的时间片用完,发生调度,切换P0上处理机运行。
+ 而代码①不会卡住P0P0可以正常访问临界区,在P0访问临界区期间即时切换回P1P1依然会卡在⑤。
+ 只有P0在退出区将turn改为1后P1才能进入临界区。
+ `turn`的初值为$0$,即刚开始只允许$0$号进程进入临界区。
+ 若$P_1$先上处理机运行,则会一直卡在⑤,无法使用临界资源。
+ 直到$P_1$的时间片用完,发生调度,切换$P_0$上处理机运行。
+ 而代码①不会卡住$P_0$$P_0$可以正常访问临界区,在$P_0$访问临界区期间即时切换回$P_1$$P_1$依然会卡在⑤。
+ 只有$P_0$在退出区将`turn`改为1后$P_1$才能进入临界区。
+ 因此,该算法可以实现同一时刻最多只允许一个进程访问临界。
缺点:
+ turn表示当前允许进入临界区的进程号而只有当前允许进入临界区的进程在访问了临界区之后才会修改turn的值。
+ 也就是说,对于临界区的访问,一定是按P0→P1→P0→P1→……这样轮流访问。
+ 如果P0进程一直不访问临界区,那么临界资源会被P0一直占用。
+ `turn`表示当前允许进入临界区的进程号,而只有当前允许进入临界区的进程在访问了临界区之后,才会修改`turn`的值。
+ 也就是说,对于临界区的访问,一定是按$P_0\rightarrow P1\rightarrow P0\rightarrow P1\rightarrow\cdots$这样轮流访问。
+ 如果一个进程一直不访问临界区,那么临界资源会被这个进程一直占用。
+ 违背了空闲让进的原则。
#### 双标志先检查法
算法思想设置一个布尔型数组flag[\],数组中各个元素用来标记各进程想进入临界区的意愿,比如`flag[0] = ture`意味着0号进程 P0现在想要进入临界区。每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自身对应的标志 flag[i]设为true之后开始访问临界区。
算法思想:设置一个布尔型数组`flag[]`,数组中各个元素用来标记各进程想进入临界区的意愿,比如`flag[0]=ture`意味着$0$号进程 $P_0$现在想要进入临界区。每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自身对应的标志`flag[i]`设为`true`,之后开始访问临界区。
```cpp
// 表示进入临界区意愿的数组
@@ -625,25 +634,35 @@ flag [0] = false;
flag [1] = false;
// P0进程
// 进入区
while (flag[1]); // ①
flag[0] = true; // ②
// 临界区
critical section; // ③
// 退出区
flag [0] = false; // ④
// 剩余区
remainder section;
// P1进程
// 进入区
while (flag[0]); // ⑤
flag[1] = true; // ⑥
// 临界区
critical section; // ⑦
// 退出区
flag[1] = false; // ⑧
// 剩余区
remainder section;
```
若按照①⑤②⑥③⑦……的顺序执行P0和P1将会同时访问临界区。因此双标志先检查法的主要问题是违反忙则等待原则
优点:不需要交替进入
缺点:若按照①⑤②⑥③⑦……的顺序执行,$P_0$和$P_1$将会同时访问临界区,违反忙则等待原则。即在检查对方的$flag$后和切换自己的$flag$前之间有一段时间,结果都会检查通过,检查和修改操作不能一次性进行。
#### 双标志后检查法
算法思想:双标志先检查法的改版。前一个算法的问题是先“检查”后“上锁”,但是这两个操作又无法一气呵成,因此导致了两个进程同时进入临界区的问题。因此,人们又想到先“上锁”后“检查”的方法,来避免上述问题。
算法思想:双标志先检查法的改版。前一个算法的问题是先“检查”后“上锁”,但是这两个操作又无法一气呵成,因此导致了两个进程同时进入临界区的问题。因此,人们又想到先“上锁”后“检查”的方法,来避免上述问题。即先将自己标志位设置为`true`再检查对方的标志位,若对方也为`true`则等待,否则进入临界区。
```cpp
// 表示进入临界区意愿的数组
@@ -653,27 +672,35 @@ flag [0] = false;
flag [1] = false;
// P0进程
// 进入区
flag[0] = true; // ①
while (flag[1]); // ②
// 临界区
critical section; // ③
// 退出区
flag [0] = false; // ④
// 剩余区
remainder section;
// P1进程
// 进入区
flag[1] = true; // ⑤
while (flag[0]); // ⑥
// 临界区
critical section; // ⑦
// 退出区
flag[1] = false; // ⑧
// 剩余区
remainder section;
```
若按照①⑤②⑥③⑦……的顺序执行,P0和P1都不能访问临界区。
进程同时想进入临界区,按照①⑤②⑥③⑦……的顺序执行,则都发现对方标志位是`true`$P_0$和$P_1$都不能访问临界区。
因此,双标志后检查法虽然解决了“忙则等待”的问题,但是又违背了“空闲让进”和“有限等待”原则,会因各进程都长期无法访问临界资源而产生“饥饿”现象。
#### Peterson算法
算法思想双标志后检查法中两个进程都争着想进入临界区但是谁也不让谁最后谁都无法进入临界区。Gary L.Peterson想到了一种方法如果双方都争着想进入临界区那可以让进程尝试“孔融让梨”主动让对方先使用临界区。
算法思想:双标志后检查法中,两个进程都争着想进入临界区,但是谁也不让谁,最后谁都无法进入临界区。`Gary\,L.Peterson`想到了一种方法,如果双方都争着想进入临界区,那可以让进程尝试“孔融让梨”,主动让对方先使用临界区。每个进程再设置自己的标志后再设置一个变量`turn`不允许进入标志的值,再同时检测另一个进程的状态标志位与不允许进入标志。
```cpp
// 表示进入临界区意愿的数组初始值都是false
@@ -682,26 +709,37 @@ bool flag[2];
int turn = 0;
// P0进程:
// 进入区
flag[0] = true; // ①
turn = 1; // ②
// 当对方也想进且本进程已经让出优先权就等待
while (flag[1] && turn==1); //③
// 临界区
critical section; // ④
// 退出区
flag[0] = false; // ⑤
// 剩余区
remainder section;
// P1进程:
// 进入区
flag[1] = true; // ⑥ 表示自己想进入临界区
turn = 0; // ⑦ 可以优先让对方进入临界区
while (flag[0] && turn==0); // ⑧ 对方想进,且最后一次是自己“让梨",那自己就循环等待
// 临界区
critical section; // ⑨
// 退出区
flag[1] = false; // ⑩ 访问完临界区,表示自己已经不想访问临界区了
// 剩余区
remainder section;
```
Peterson算法用软件方法解决了进程互斥问题遵循了空闲让进、忙则等待、有限等待三个原则,但是依然未遵循让权等待的原则。
$Peterson$算法用软件方法解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待三个原则但是依然未遵循让权等待的原则。
### 进程互斥的硬件实现
称为**低级方法**,或**元方法**。
#### 中断屏蔽
+ 利用“开/关中断指令”实现(与原语的实现思想相同,即在某进程开始访问临界区到结束访问为止都不允许被中断,也就不能发生进程切换,因此也不可能发生两个同时访问临界区的情况)。
@@ -712,11 +750,13 @@ Peterson算法用软件方法解决了进程互斥问题遵循了空闲让进
+ 不适用于多处理机。
+ 只适用于操作系统内核进程,不适用于用户进程(因为开/关中断指令只能运行在内核态,这组指令如果能让用户随意使用会很危险)。
#### TS指令
#### 硬件指令
简称TS指令也有地方称TestAndSetLock指令或TSL指令。
$TS$指令,也有地方称$TestAndSetLock$指令,或$TSL$指令。
TSL指令是用硬件实现的执行的过程不允许被中断只能一气呵成。
$TSL$指令是用硬件实现的原子操作,执行的过程不允许被中断,只能一气呵成。
读出指定标志后把该标志设置为真。
```cpp
// 布尔型共享变量lock表示当前临界区是否被加锁
@@ -724,8 +764,8 @@ TSL指令是用硬件实现的执行的过程不允许被中断只能一
bool TestAndSet (bool *lock){
bool old;
old = *lock; // old用来存放lock原来的值
*lock = true; // 无论之前是否已加锁,都将Lock设为true
return old; // 返回Lock原来的值
*lock = true; // 无论之前是否已加锁,都将lock设为true
return old; // 返回lock原来的值
}
// 以下是使用TSL指令实现互斥的算法逻辑
@@ -735,23 +775,22 @@ lock = false; // "解锁""
...
```
+ 若刚开始lock是false则TSL返回的old值为falsewhile循环条件不满足直接跳过循环进入临界区
+ 若刚开始lock是true则执行TLS后old返回的值为truewhile循环条件满足会一直循环,直到当前访问临界区的进程在退出区进行“解锁”
+ 每个临界资源都有一个共享布尔变量`lock``true`表示被占用,`false`表示空闲,是初值
+ 若刚开始`lock``false`,则$TSL$返回的`old`值为`false``while`循环条件满足,直接跳过循环,进入临界区
+ 若刚开始`lock``true`,则执行$TSL$后`old`返回的值为`true``while`循环条件满足,会一直循环,直到当前访问临界区的进程在退出区进行“解锁”。
相比软件实现方法TSL指令把“上锁”和“检查”操作用硬件的方式变成了一气呵成的原子操作。
相比软件实现方法,$TSL$指令把“上锁”和“检查”操作用硬件的方式变成了一气呵成的原子操作。
+ 优点:
+ 实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞。
+ 适用于多处理机环境。
+ 缺点:
+ 不满足“让权等待”原则。
+ 暂时无法进入临界区的进程会占用$CPU$并循环执行TSL指令从而导致“忙等”。
+ 暂时无法进入临界区的进程会占用$CPU$并循环执行$TSL$指令,从而导致“忙等”。
#### Swap指令
$Swap$指令有的地方也叫$Exchange$指令,或简称$XCHG$指令。
有的地方也叫Exchange指令或简称XCHG指令
Swap指令是用硬件实现的执行的过程不允许被中断只能一气呵成。
$Swap$指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成
```cpp
// Swap指令的作用是交换两个变量的值
@@ -762,18 +801,22 @@ Swap (bool *a,bool *b){
*b = temp;
}
//以下是用Swap指令实现互斥的算法逻辑
// 以下是用Swap指令实现互斥的算法逻辑
// lock表示当前临界区是否被加锁
// 上锁
bool old = true;
while (old == true){
Swap (&lock,&old);
}
...
// 解锁
lock = false;
...
```
逻辑上来看SwapTSL并无太大区别都是先记录下此时临界区是否已经被上锁记录在old变量上)再将上锁标记lock设置为true最后检查old如果oldfalse则说明之前没有别的进程对临界区上锁则可跳出循环进入临界区。
逻辑上来看$Swap$和$TSL$并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在`old`变量上,再将上锁标记`lock`设置为`true`,最后检查`old`,如果`old``false`则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区。
硬件方法的特点:
+ 优点:
+ 实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞。
@@ -782,7 +825,7 @@ lock = false;
+ 不满足“让权等待”原则。
+ 暂时无法进入临界区的进程会占用$CPU$并循环执行$TSL$指令,从而导致“忙等”。
### 信号量机制
### 信号量
之前软硬件实现的进程互斥都无法解决让权等待问题,所以$Dijkstra$提出实现进程互斥和同步的方法——信号量机制。
@@ -791,19 +834,20 @@ lock = false;
+ 用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。
+ 信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量)可以用一个信号量来表示系统中某种资源的数量比如系统中只有一台打印机就可以设置一个初值为1的信号量。
+ 原语是一种特殊的程序段,其执行只能一气呵成,不可被中断。原语是由关中断/开中断指令实现的。软件解决方案的主要问题是由“进入区的各种操作无法一气呵成”,因此如果能把进入区、退出区的操作都用“原语”实现,使这些操作能“一气呵成”就能避免问题。
+ 一对原语: wait(S)原语和signal(S)原语可以把原语理解为我们自己写的函数函数名分别为waitsignal括号里的信号量s其实就是函数调用时传入的一个参数。
+ waitsignal原语常简称为P、V操作来自荷兰语proberenverhogen)。因此做题的时候常把wait(S)signal(S)两个操作分别写为P(S)V(S)。
+ 一对原语`wait(S)`原语和`signal(S)`原语(`S`表示整型量),可以把原语理解为我们自己写的函数,函数名分别为`wait``signal`,括号里的信号量`S`其实就是函数调用时传入的一个参数。
+ $wait$、$signal$原语常简称为$P$、$V$操作(来自荷兰语$proberen$和$verhogen$。因此,做题的时候常把$wait(S)$、$signal(S)$两个操作分别写为$P(S)$、$V(S)$
#### 整型信号量
用一个整数型的变量作为信号量,用来表示系统中某种资源的数量。
与普通整数变量的区别:对信号量的操作只有三种,即初始化、P操作、V操作。
与普通整数变量的区别:对信号量的操作只有三种,即初始化、$P$操作、$V$操作。
```cpp
// 加入某计算机系统中有一台打印机打印机被争用如果有n台打印机则S初始为为n
//初始化整型信号量s,表示当前系统中可用的打印机资源数
int S = 1;
//初始化整型信号量S,表示当前系统中可用的打印机资源数
#define n 1;
int S = n;
// wait原语相当于“进入区”
void wait (int &S){
while (S <= 0); // 如果资源数不够,就一直循环等待
@@ -822,9 +866,9 @@ wait(S); // 进入区,申请资源
signal(S); // 退出区,释放资源
```
“检查”和“上锁”一气呵成,避免了并发、异步导致的问题。
“检查”和“上锁”一气呵成避免了并发、异步导致的问题。
存在的问题:不满足“让权等待”原则,会发生“忙等”。
存在的问题:只要信号量$S\leqslant0$就不断测试,不满足“让权等待”原则,会发生“忙等”。
#### 记录型信号量
@@ -856,18 +900,18 @@ void signal (semaphore S) {
}
```
+ 在考研题目中wait(S)signal(S)也可以记为P(S)V(S),这对原语可用于实现系统资源的“申请”和“释放”。
+ S.value的初值表示系统中某种资源的数目。
+ 对信号量S的一次Р操作意味着进程请求一个单位的该类资源,因此需要执行`S.value--`,表示资源数减1S.value <0时表示该类资源已分配完毕因此进程应调用block原语进行自我阻塞当前运行的进程从运行态>阻塞态),主动放弃处理机,并插入该类资源的等待队列s.L中。可见,该机制遵循了“让权等待”原则,不会出现“忙等”现象。
+ 对信号量S的一次V操作意味着进程释放一个单位的该类资源,因此需要执行`S.value++`,表示资源数加1若加1后仍是S.value<=0表示依然有进程在等待该类资源因此应调用wakeup原语唤醒等待队列中的第一个进程被唤醒进程从阻塞态→就绪态
+ 在考研题目中`wait(S)``signal(S)`也可以记为`P(S)``V(S)`,这对原语可用于实现系统资源的“申请”和“释放”。
+ `S.value`的初值表示系统中某种资源的数目。
+ 对信号量`S`的一次$Р$操作意味着进程请求一个单位的该类资源,因此需要执行`S.value--`,表示资源数减$1$,当`S.value<0`时表示该类资源已分配完毕,因此进程应调用`block`原语进行自我阻塞(当前运行的进程从运行态变为阻塞态),主动放弃处理机,并插入该类资源的等待队列`S.L`中。可见,该机制遵循了“让权等待”原则,不会出现“忙等”现象。
+ 对信号量`S`的一次$V$操作意味着进程释放一个单位的该类资源,因此需要执行`S.value++`,表示资源数加$1$,若加$1$后仍是`S.value<=0`,表示依然有进程在等待该类资源,因此应调用`wakeup`原语唤醒等待队列中的第一个进程(被唤醒进程从阻塞态→就绪态)。
#### 信号量机制实现进程互斥
1. 分析并发进程的关键活动,划定临界区(如对临界资源打印机的访问就应放在临界区)。
2. 设置互斥信号量mutex初值为1
3. 互斥信号量取值为0或10代表上锁,1代表释放。
4. 在临界区之前执行P(mutex)。
5. 在临界区之后执行V(mutex)。
2. 设置互斥信号量`mutex`,初值为$1$,表示一次只能有一个进程访问
3. 互斥信号量取值为$0$或$1$$0$代表上锁,$1$代表释放。
4. 在临界区之前执行`P(mutex)`
5. 在临界区之后执行`V(mutex)`
6. 注意:对不同的临界资源需要设置不同的互斥信号量。
```cpp
@@ -896,11 +940,11 @@ P2(){
进程同步:要让各并发进程按要求有序地推进。
1. 分析什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两个操作(或两句代码)。
2. 设置同步信号量S,初始为0
1. 分析什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两个操作(或两句代码)。如先$P_1$后$P_2$。
2. 设置同步信号量`S`,初始为$0$
3. 同步信号量为整数,释放加一,占用减一。
4. 在“前操作”之后执行V(S)。
5. 在“后操作”之前执行P(S)。
4. 在“前操作”之后执行`V(S)`
5. 在“后操作”之前执行`P(S)`
```cpp
// 信号量机制实现同步
@@ -920,19 +964,19 @@ P2(){
}
```
+ 若先进行进程P1
+ 执行到V(S)操作则S++S=1。
+ 之后当进行P2执行到P(S)操作时由于S=1表示有可用资源会执行S--S的值变回0P2进程不会执行block原语而是继续往下执行代码4
+ 若先进行进程P2
+ 执行到P(S)操作由于S=0S--后S=-1表示此时没有可用资源因此P操作中会执行block原语主动请求阻塞。
+ 时间片用完后进行进程P1,之后当执行完代码2继而执行V(S)操作S++使S变回0
+ 由于此时有进程在该信号量对应的阻塞队列中,因此会在V操作中执行wakeup原语唤醒P2进程。这样P2就可以继续执行代码4了。
+ 若先进行进程$P_1$
+ 执行到`V(S)`操作,则`S++``S=1`
+ 之后当进行$P_2$,执行到`P(S)`操作时,由于`S=1`,表示有可用资源,会执行`S--``S`的值变回$0$$P_2$进程不会执行`block`原语,而是继续往下执行代码$4$
+ 若先进行进程$P_2$
+ 执行到`P(S)`操作,由于`S=0``S--``S=-1`,表示此时没有可用资源,因此$P$操作中会执行`block`原语,主动请求阻塞。
+ 时间片用完后进行进程$P_1$,之后当执行完代码$2$,继而执行`V(S)`操作,`S++`,使`S`变回$0$
+ 由于此时有进程在该信号量对应的阻塞队列中,因此会在$V$操作中执行`wakeup`原语,唤醒$P_2$进程。这样$P_2$就可以继续执行代码$4$了。
#### 信号量机制实现前驱关系
前驱关系其实是多组同步。
进程P1中有句代码S1P2中有句代码S2……P6中有句代码S6。这些代码要求按如下前驱图所示的顺序来执行:
进程$P_1$中有句代码$S_1$$P_2$中有句代码$S_2$……$P_6$中有句代码$S_6$。这些代码要求按如下前驱图所示的顺序来执行:
```terminal
|-S4-|
@@ -945,12 +989,14 @@ S1-| |-S5-|-S6
其实每一对前驱关系都是一个进程同步问题(需要保证一前一后的操作)因此:
1. 要为每一对前驱关系各设置一个同步变量。
2. 在“前操作”之后对相应的同步变量执行V操作
3. 在“后操作”之前对相应的同步变量执行Р操作
2. 在“前操作”之后对相应的同步变量执行$V$操作。类似表示当前动作$S_i$已经完成
3. 在“后操作”之前对相应的同步变量执行$Р$操作。类似检测前一个动作$S_i$是否完成
S1-S2之间信号量为a=0S1-S3之间的信号量b=0S2-S4之间信号量为c=0S2-S5之间信号量为d=0S4-S6之间信号量为e=0S5-S6之间信号量为f=0S3-S6之间信号量为g=0。
$S_1-S_2$之间信号量为$a=0$$S_1-S_3$之间的信号量$b=0$$S_2-S_4$之间信号量为$c=0$$S_2-S_5$之间信号量为$d=0$$S_4-S_6$之间信号量为$e=0$$S_5-S_6$之间信号量为$f=0$$S_3-S_6$之间信号量为$g=0$
一条线段靠近根的对当前信号量进行V操作靠近尾的对当前信号量进行P操作
个$S_i$操作都设置一个进程$P_i$
每一条线段靠近根的对当前信号量进行$V$操作,靠近尾的对当前信号量进行$P$操作。
再将每个代码结点旁边的操作聚拢在一起,就是每个进程所应该执行的操作。
@@ -965,7 +1011,7 @@ P1() {
P2() {
...
P(a);
s2;
S2;
V(c);
V(d);
...
@@ -1003,7 +1049,7 @@ P6() {
### 进程同步与互斥应用
PV操作题目分析步骤
$PV$操作题目分析步骤:
1. 关系分析。找出题目中描述的各个进程,分析它们之间的同步、互斥关系。
2. 整理思路。根据各进程的操作流程确定P、V操作的大致顺序。
@@ -1351,15 +1397,44 @@ Pi() {
1. 局部于管程的共享数据结构(临界区)说明。
2. 对该数据结构进行操作的一组过程。
3. 对局部于管程的共享数据设置初始值的语句。
4. 管程有一个名字。
4. 管程名字。
管程的基本特征:
管程的基本特征
1. 局部于管程的数据只能被局部于管程的过程所访问。
2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据。(即管程中定义的共享数据结构只能被管程定义的函数所修改)
3. 每次仅允许一个进程在管程内执行某个内部过程。(在一个时刻内一个函数只能被一个进程使用)
#### 处理生产者消费者问题
#### 条件变量
通过条件变量来实现阻塞进程。由于一个进程被阻塞的原因可能有多个,所以管程中设置多个条件变量,每个条件变量保存一个等待队列,用于记录因该条件变量而阻塞的所有进程。对条件变量只有两个操作:`wait``signal`
+ `x.wait`:当`x`对应的条件不满足时,正在调用管程的进程调用`x.wait`将自己插入`x`条件的等待队列,并释放管程。此时其他进程可以使用该管程。
+ `x.signal``x`对应的条件发生了变化,则调用`x.signal`,唤醒一个因`x`条件而阻塞的进程。
```cpp
monitor Demo {
S;
condition x; //定义一个条件变量×
init code(){}
take away(){
if(S<=0) x.wait();
// 资源不够,在条件变量×上阻塞等待
;
}
give_back(){
;
if() x.signal; //唤醒一个阻塞进程
}
}
```
条件变量与信号量:
+ 相似点:条件变量的$wait/signal$操作类似于信号量的$PV$操作,可以实现进程的阻塞/唤醒。
+ 不同点:条件变量是“没有值”的,仅实现了“排队等待”功能;而信号量是“有值”的,信号量的值反映了剩余资源数,而在管程中,剩余资源数用共享数据结构记录。
#### 处理同步互斥问题
1. 需要在管程中定义共享数据(如生产者消费者问题的缓冲区)。
2. 需要在管程中定义用于访问这些共享数据的“入口”—―其实就是一些函数(如生产者消费者问题中,可以定义一个函数用于将产品放入缓冲区,再定义一个函数用于从缓冲区取出产品)。