一、进程同步、互斥的基本概念

1. 什么是进程同步

进程具有异步性的特征。异步性是指各并发执行的进程以各自独立的、不可预知的速度向前推进。举一个线程通信的例子,管道通信:

读进程和写进程并发运行,由于并发必然导致异步性,因此“写数据”和“读数据”两个操作执行的先后顺序是不确定的。而实际应用中,又必须按照“写数据→读数据”的顺序来执行。如何解决这种异步问题,就是“进程同步”所讨论的内容;

同步亦称直接制约关系,它是指为了完成某种任务而建立的两个或多个进程,这些进程因为需要在某些位置协调它们的工作次序而产生的制约关系。进程间的直接制约关系就是源于它们之间的相互合作。

2. 什么是进程互斥

进程的并发需要“共享”的支持。各个并发执行的进程不可避免地需要共享一些系统资源(比如内存、又比如打印机、摄像头这样的 I/O 设备);

两种资源共享方式:

  • 互斥共享方式:系统中的资源,虽然可以提供给多个进程使用,但一个时间段内只允许一个进程访问该资源;
  • 同时共享方式:系统中的某些资源,允许同一时间段内由多个进程“同时”对它们进行访问。

我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备(比如摄像头、打印机)都属于链接资源。此外还有许多变量、数据、内存缓冲区等都属于临界资源。

对临界资源的访问,必须互斥地进行。互斥,亦称间接制约关系。进程互斥指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待。当前访问临界资源的进程访问结束,释放该资源之后,另一个进程才能去访问临界资源。

对临界资源的互斥访问,可以在逻辑上分为如下四个部分:

do {entry section;     // 进入区critical section;  // 临界区exit section;      // 退出区remainder section; // 剩余区
} while(true)

临界区是进程中访问临界资源的代码段;进入区和退出区是负责实现互斥代码段;临界区也可称为“临界段”。

为了实现对临界资源的互斥访问,同时保证系统整体性能,进程互斥的实现需要遵循以下原则:

  • 空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区;
  • 忙则等待:当已有进程进入临界区时,其他试图进入临界区的进程必须等待;
  • 有限等待:对请求访问的进程,应保证能在有限时间内进入临界区,保证不会饥饿(受惠的是本线程);
  • 让权等待:当进程不能进入临界区时,应立即释放处理机,防止进程忙等待(受惠的是其他线程)。

二、进程互斥的实现方式

1. 软件实现方式

1.1 单标志法

算法思想:两个进程中的一个线程在访问临界区后会把使用临界区的权限转交给另一个进程,也就是说每个进程进入临界区的权限只能被另一个进程赋予。

int turn = 0; // turn 表示当前允许进入临界区的进程号

P0进程:

while (turn != 0); ①
critical section;  ②
turn = 1;          ③
remainder section; ④

P1 进程:

while (turn != 1); ⑤ // 进入区
critical section;  ⑥ // 临界区
turn = 0;          ⑦ // 退出区
remainder section; ⑧ // 剩余区

turn 的初值为 0,即刚开始只允许 0 号进程进入临界区。 若 P1 先上处理机运行,则会一直卡在⑤。直到 P1 的时间片用完,发生调度,切换 P0 上处理机运行。代码①不会卡住 P0,P0 可以正常访问临界区,在 P0 访问临界区期间即时切换回 P1,P1 依然会卡在⑤。只有 P0 在退出区将 turn 改为 1 后,P1 才能进入临界区。

因此,该算法可以实现“同一时刻最多只允许一个进程访问临界区”。然而这种必须“轮流访问”带来的问题是,如果此时允许进入临界区的进程是 P0,而 P0 一直不访问临界区,那么虽然此时临界区空闲,但是并不允许 P1 访问。

所以,单标志法存在的主要问题是:违背空闲让进原则。

1.2 双标志先检查

算法思想:设置一个布尔型数组flag[],数组中各个元素用来标记各进程想进入临界区的意愿,比如flag[0] = true意味着 0 号进程 P0 现在想要进入临界区。每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自身对应的标志flag[i]设为true,之后开始访问临界区。

bool flag[2]; // 表示进入临界区意愿的数组
flag[0] = false;
flag[1] = false; // 刚开始设置为两个进程都不想进入临界区

P0进程:

while (flag[1]);   ①
flag[0] = true;    ②
critical section;  ③
flag[0] = false;   ④
remainder section;

P1进程:

while (flag[0]);   ⑤ // 如果此时 P0 想进入临界区,P1 就一直循环等待
flag[1] = true;    ⑥ // 标记为 P1 进程想要进入临界区
critical section;  ⑦ // 访问临界区
falg[1] = false;   ⑧ // 访问完临界区,修改标记为 P1 不想使用临界区
remainder section;

若按照 ①⑤②⑥③⑦… 的顺序执行,P0 和 P1 将会同时访问临界区。

因此,双标志先检查法的主要问题是:违反“忙则等待”原则。原因在于,进入区的“检查”和“上锁”两个处理不是一气呵成的。“检查”后,“上锁”前可能发生进程切换。

1.3 双标志后检查

算法思想:双标志先检查法的改版。前一个算法的问题是先“检查”后“上锁”,但是这两个操作又无法一气呵成,因此导致了两个进程同时进入临界区的问题。因此,人们又想到先“上锁”后“检查”的方法,来避免上述问题。

bool flag[2]; // 表示进入临界区意愿的数组
flag[0] = false;
flag[1] = false; // 刚开始设置为两个进程都不想进入临界区

P0进程:

flag[0] = true;   ①
while (flag[1]);  ②
critical section; ③
flag[0] = false;  ④
remainder section;

P1进程:

flag[1] = true;   ⑤ // 标记为 P1 进程想要进入临界区
while (flag[0]);  ⑥ // 如果 P0 也想进入临界区,则 P1 循环等待
critical section; ⑦ // 访问临界区
flag[1] = false;  ⑧ // 访问完临界区,修改标记为 P1 不想使用临界区
remainder section;

若按照 ①⑤②⑥… 的顺序执行,P0 和 P1 将都无法进入临界区。

因此,双标志后检查法虽然解决了“忙则等待”的问题,但是又违背了“空闲让进”和“有限等待”原则,会因各进程都长期无法访问临界资源而产生“饥饿”现象。两个进程都争着想进入临界区,但是谁也不让谁,最后谁都无法进入临界区

1.4 Peterson 算法

算法思想:结合双标志法、单标志法的思想。如果双方都争着想进入临界区,那可以让进程尝试谦让。

bool flag[2]; // 表示进入临界区意愿的数组,初始值都是 false
int turn = 0; // turn 表示优先让哪个进程进入临界区

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 算法用软件方法解决了进程互斥问题,遵循了空闲让进、忙则等待、有限等待三个原则,但是依然未遵循让权等待的原则

2. 硬件实现方式

软的不行来硬的,一般硬件实现方式会比软件实现的方式更加高效可靠,接下来三种硬件实现进程互斥的方式。

2.1 中断屏蔽方法

利用“开/关中断指令”实现(与原语的实现思想相同,即在某进程开始访问临界区到结束访问为止都不允许被中断,也就不能发生进程切换,因此也不可能发生两个同时访问临界区的情况)

...
关中断;
临界区;
开中断;
...

优点:简单、高效

缺点:不适用于多处理机;只适用于操作系统内核进程,不适用于用户进程(因为开/关中断指令只能运行在内核态,这组指令如果能让用户随意使用会很危险)

2.2 TestAndSet(TS指令/TSL指令)

简称 TS 指令,有时也称TestAndSetLock指令,简称TSL指令。

TSL 指令是用硬件实现的,执行的过程不允许被中断,只能一气呵成。以下是用 C 语言描述的逻辑:

// 布尔型共享变量 lock 表示当前临界区是否被加锁
// true 表示已加锁,false 表示未加锁
bool TestAndSet (bool *lock) {bool old;old = *lock;  // old 用来存放 lock 原来的值*lock = true; // 无论之前是否已加锁,都将 lock 设为 truereturn old;   // 返回 lock 原来的值
}// 以下是使用 TSL 指令实现互斥的算法逻辑
while (TestAndSet (&lock)); // “上锁”并“检查”
临界区代码段...
lock = false; // “解锁”
剩余区代码段

若刚开始 lockfalse, 。则 TSL 返回的 old 值为 false,while 循环条件不满足,直接跳过循环,进入临界区。若刚开始 Iock 足 true ,则执行 TLS 后 old 返回的值为true, while 循环条件满足,会一直循环,直到当前访问临界区的进程在退出区进行“解锁”。

相比软件实现方法,TSL 指令把 “上锁”和“检查”操作用硬件的方式变成了一气呵成的原则操作。

优点:实现简单,无需像软件实现方法那样严格检查是否会有逻辑漏洞;适用于多处理机环境;

缺点:不满足“让权等待”原则,暂时无法进入临界区的进程会占用 CPU 并循环执行 TSL 指令,从而导致“忙等”。

2.3 Swap指令

也称 Exchange 指令,或简称 XCHG 指令。

Swap 指令是用硬件实现的,执行的过程中不允许被中断,只能一气呵成。以下是用 C 语言描述的逻辑

// Swap 指令的作用是交换两个变量的值
Swap (bool *a, bool *b) {bool temp;temp = *a;*a = *b;*b = temp;
}// 以下是使用 Swap 指令实现互斥的算法逻辑
// lock 表示当前临界区是否被加锁
bool old = true;c
while (old == true)Swap (&lock, &old);
临界区代码段...
lock = false;
剩余区代码段...

逻辑上来看 Swap 和 TSL 并无太大区别,都是先记录下此时临界区是否已经被上锁(记录在 old 变量上),再将上锁标记 lock 设置为 true,最后检查 old,如果 old 为 false 则说明之前没有别的进程对临界区上锁,则可跳出循环,进入临界区。

三、信号量机制

回顾上面的几种进程互斥的实现方式:在双标志先检查法中,进入区的“检查”和“上锁”操作无法一气呵成,从而导致了两个进程有可能同时进入临界区的问题。所有的解决方案都无法实现“让权等待”。因此Dijkstra大佬提出了一种卓有成效的实现进程互斥、同步的方法——信号量机制

1. 信号量机制的概念

用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现了进程互斥、进程同步。信号量其实就是一个变量(可以是一个整数,也可以是更复杂的记录型变量),可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为 1 的信号量。

一对原语wait(S)原语和signal(S)原语,可以把原语理解为我们自己写的函数,函数名为 wait 和 signal,括号里的信号量 S 其实就是函数调用时传入的一个参数。wait、signal 原语简称为 P、V 操作(来自荷兰语 proberen 和 verhogen)。

2. 信号量机制类型

2.1 整形信号量

用一个整数型的变量作为信号量,用来表示系统中某种资源的数量。

Eg:某计算机系统中有一台打印机…

int S = 1; // 初始化整形信号量s,表示当前系统中可用的打印机志愿数。void wait (int S) {  // wait 原语,相当于“进入区”while (S <= 0);  // 如果资源数不够,就一直循环等待S = S - 1;       // 如果资源数够,则占用一个资源
}void signal (int S) { // signal 原语,相当于“退出区”S = S + 1;        // 使用完资源后,在退出区释放资源
}

现在有多个进程要去争用打印机:

进程 P0:
...
wait(S);           // 进入区,申请资源
使用打印机资源...     // 临界区,访问资源
signal(S);         // 退出区,释放资源
...
====================================
进程 P1:
...
wait(S);           // 进入区,申请资源
使用打印机资源...     // 临界区,访问资源
signal(S);         // 退出区,释放资源
...
====================================
进程 Pn:
...
wait(S);
使用打印机资源...
signal(S);
...

这种方式还是有一定的缺陷,像wait函数种的while (S <= 0) 这段代码就存在问题:不满足“让权等待”原则,会发生“忙等”。

2.2 记录型信号量

整形信号量的缺陷是“忙等”问题,因此后来人提出“记录型信号量”,即用记录型数据结构表示的信号量。

/* 记录型信号量的定义 */
typedef struct {int value;         // 剩余资源数struct process *L; // 等待队列
} semaphore;
/* 某进程需要使用资源时,通过 wait 原语申请 */
void wait (semaphore S) {S.value--;if (S.value < 0) {/*如果剩余的资源数不够,使用 block 原语使进程从运行态进入阻塞态,并挂到信号量 S 的等待队列(即阻塞队列)中*/block (S.L);}
}
/* 进程使用完资源后,通过 signal 原语释放 */
void signal (semaphore S) {s.value ++;if (S.value <= 0) {/*释放资源后,若还有别的进程在等待这种资源,则使用 wakeup 原语唤醒等待队列中的一个进程,该进程从阻塞态变为就绪态*/wakeup(S.L);}
}

对信号量 S 的一次 P 操作意味着进程请求一个单位的该类资源,因此需要执行 S.value--,表示资源数减 1,当 S.value < 0 时表示该类资源已分配完毕,因此进程应调用 block 原语进行自我阻塞(当前运行的进程从运行态→阻塞态),主动放弃处理机,并插入该类资源的等待队列 S.L 中。可见,该机制遵循了“让权等待”原则,不会出现“忙等”现象。 同理,对信号量的一次 V 操作反之。

3. 信号量机制的使用

3.1 信号量机制实现进程互斥

  1. 分析并发进程的关键活动,划定临界区(如:对临界资源打印机的访问就应放在临界区)
  2. 设置互斥信号量 mutex,初值为1
  3. 在进入区 P(mutex)——申请资源
  4. 在退出区 V(mutex)——释放资源

注意:对不同的临界资源需要设置不同的互斥信号量。P、V 操作必须成对出现。

P1() {...P(mutex); // 使用临界资源前需要加锁临界区代码段V(mutex); // 使用临界资源后需要解锁...
}P2() {...P(mutex);临界区代码段V(mutex);...
}

3.2 信号量机制实现进程同步

进程同步:要让各并发进程按要求有序地推进。

用信号量实现进程同步:

  1. 分析什么地方需要实现“同步关系”,即必须保证“一前一后”执行的两个操作(或两句代码)
  2. 设置同步信号量 S,初始为0
  3. 在“前操作”之后执行 V(S)
  4. 在“后操作”之前执行 P(S)
/* 信号量机制实现同步 */
semaphore S = 0; // 初始化同步信号量,初始值为0
P1() {代码1;代码2;V(S); // 释放资源代码3;
}P2() {cP(S);代码4;代码5;代码6;
}

保证了代码4一定是在代码2之后执行

3.3 信号量机制实现前驱关系

进程 P1 中有句代码 S1,P2 中有句代码 S2 … P6 中有句代码 S6。这些代码要求按如下前驱图所示的顺序来执行:

其实每一对前驱关系都是一个进程同步问题(需要保证一前一后的操作),因此:

  1. 要为每一对前驱关系各设置一个同步信号量
  2. 在“前操作”之后对相应的同步信号量执行 V 操作
  3. 在“后操作”之前对相应的同步信号量执行 P 操作

四、经典同步问题

1. 生产者-消费者问题

系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。

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

semaphore mutex = 1; // 互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n; // 同步信号量,表示空闲缓冲区的数量
semaphore full = 0;  // 同步信号量,表示产品的数量,也即非空缓冲区的数量producer () {while(1) {生产一个产品;P(empty); // 消耗一个空闲缓冲区P(mutex);把产品放入缓冲区;V(mutex);V(full);  // 增加一个产品}
}consumer() {while(1) {P(full); // 消耗一个产品(非空缓冲区)P(mutex);从缓冲区取出一个产品;V(mutex);V(empty); // 增加一个空间缓冲区使用产品;}
}

2. 吸烟者问题

假设一个系统有三个抽烟者进程和一个供应者进程。每个抽烟者不停地卷烟并抽掉它,但是要卷起并抽掉一支烟,抽烟者需要有三种材料:烟草、纸和胶水。三个抽烟者中,第一个拥有烟草、第二个拥有纸、第三个拥有胶水。供应者进程无限地提供三种材料,供应者每次将两种材料放桌子上,拥有剩下那种材料的抽烟者就会卷一根烟并抽调它,并给供应者一个信号告诉完成了,供应者就会放另外两种材料在桌上,这个过程一直重复(让三个抽烟者轮流地抽烟)

本质上这题也属于“生产者-消费者”问题,更详细的说应该是“可生产多种产品的单生产者-多消费者”。

semaphore offer1 = 0; // 桌上组合一的数量
semaphore offer2 = 0; // 桌上组合二的数量
semaphore offer3 = 0; // 桌上组合三的数量
semaphore finish = 0; // 抽烟是否完成
int i = 0;            // 用于实现“三个抽烟者轮流抽烟”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);}
}

3. 读者-写者问题

允许多个进程同时对数据进行读操作,但是不允许读和写操作同时发生,并且同时只允许一个写者进程往文件中写信息。

为实现 ReaderWriter 进程间在读或写时的互斥而设置了一个互斥信号量 vmutex。另外,在设置一个整形变量 readcount 表示正在读的进程数目。由于只要有一个 Reader 进程在读,便不允许 Writer 进程去写。因此,仅当 readcount = 0,表示尚无 Reader 进程在读时,Reader 进程才需要执行 wait(mutex)操作。若wait(wmutex)操作成功,Reader进程便可去读,相应地,做readcount+1操作。同理,仅当 Reader 进程在执行了 readcount 减 1 操作后其值为 0 时,才须执行 signal(wmutex) 操作,以便让 Writer 进程写操作。又因为 readcount 是一个可被多个 Reader 进程访问的临界资源,因此,也应该为它设置一个互斥信号量 rmutex

semaphore rmutex = 1, wmutex = 1;
int readcount = 0;void reader() {do {wait(rmutex);if (readcount == 0) wait(wmutex);readcount++;signal(rmutex);...perform read operation;...wait(rmutex);readcount--;if (readcount == 0) signal(wmutex);signal(rmutex);} while (TRUE);
}void Writer() {do {wait(wmutex);perform write operation;signal(mutex);} while(TRUE);
}void main() {cobeginReader(); Writer();coend;
}

4. 哲学家进餐问题

五个哲学家围着一张圆桌,每个哲学家面前放着食物。哲学家的生活有两种交替活动:吃饭以及思考。当一个哲学家吃饭时,需要先拿起自己左右两边的两根筷子,并且一次只能拿起一根筷子。

下面是一种错误的解法,如果所有哲学家同时拿起左手边的筷子,那么所有哲学家都在等待其它哲学家吃完并释放自己手中的筷子,导致死锁。

#define N 5void philosopher(int i) {while(TRUE) {think();take(i);       // 拿起左边的筷子take((i+1)%N); // 拿起右边的筷子eat();put(i);put((i+1)%N);}
}

为了防止死锁的发生,可以设置两个条件:

  • 必须同时拿起左右两根筷子;
  • 只有在两个邻居都没有进餐的情况下才允许进餐。
#define N 5
#define LEFT (i + N - 1) % N // 左邻居
#define RIGHT (i + 1) % N    // 右邻居
#define THINKING 0
#define HUNGRY   1
#define EATING   2
typedef int semaphore;
int state[N];                // 跟踪每个哲学家的状态
semaphore mutex = 1;         // 临界区的互斥,临界区是 state 数组,对其修改需要互斥
semaphore s[N];              // 每个哲学家一个信号量void philosopher(int i) {while(TRUE) {think(i);take_two(i);eat(i);put_two(i);}
}void take_two(int i) {down(&mutex);state[i] = HUNGRY;check(i);up(&mutex);down(&s[i]); // 只有收到通知之后才可以开始吃,否则会一直等下去
}void put_two(i) {down(&mutex);state[i] = THINKING;check(LEFT); // 尝试通知左右邻居,自己吃完了,你们可以开始吃了check(RIGHT);up(&mutex);
}void eat(int i) {down(&mutex);state[i] = EATING;up(&mutex);
}// 检查两个邻居是否都没有用餐,如果是的话,就 up(&s[i]),使得 down(&s[i]) 能够得到通知并继续执行
void check(i) {         if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] !=EATING) {state[i] = EATING;up(&s[i]);}
}

五、管程

使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。

1. 管程的定义和基本实现

管程是一种特殊的软件模块,有这些部分组成:

  1. 局部于管程的共享数据说明;
  2. 对该数据结构进行操作的一组过程;
  3. 对局部于管程的共享数据设置初始值的语句;
  4. 管程有一个名字

管程的基本特征:

  1. 局部于管程的数据只能被局部于管程的过程所访问;
  2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据;
  3. 每次仅允许一个进程在管程内执行某个内部过程。

2. 用管程解决生产者消费者问题

monitor ProducerConsumercondition full, empty; // 条件变量用来实现同步(排队)void count = 0;        // 缓冲区中的产品数void insert (Item item) { // 把产品 item 放入缓冲区if (count == N)wait (full);count ++;insert_item (item);if (count == 1)signal(empty);}Item remove () { // 从缓冲区中取出一个产品if (count == 0)wait (empty);count --;if (count == N - 1)signal(full);return remove_item();}
end monitor;// 生产者进程
producer () {while(1) {item = 生产一个产品;ProdecerConsumer.insert (item);}
}// 消费者进程
consumer() {while(1) {item = ProdecerConsumer.remove();消费产品 item;}
}

3. Java中类似于管程的机制

Java 中,如果关键字 synchronized 来描述一个函数,那么这个函数同一时间段内只能被一个线程调用

static class monitor {private Item buffer[] = new Item[N];private int count = 0;public synchronized void insert (Item item) {......}
}

六、死锁

1. 死锁的基本概念

死锁就是各进程互相等待对方手里的资源,导致各进程都阻塞,无法向前推进;

1.1 死锁、饥饿与死循环之间的区别

  • 死锁:至少是两个进程一起死锁,死锁进程处于阻塞态
  • 饥饿:可以只有一个进程饥饿,饥饿进程可能阻塞也可能就绪
  • 死循环:可能只有一个进程发生死循环,死循环的进程可上处理机

死锁和饥饿是操作系统要解决的问题,死循环是应用程序员要解决的。

1.2 死锁产生的必要条件

  1. 互斥条件:对必须互斥使用的资源的争抢才会导致死锁
  2. 不剥夺条件:进程保持的资源只能主动释放,不可强行剥夺
  3. 请求和保持条件:保持着某些资源不放的同时,请求别的资源
  4. 循环等待条件:存在一种进程资源的循环等待链;循环等待未必死锁,死锁一定有循环等待。

1.3 死锁的处理策略

  1. 预防死锁:破坏死锁的四个必然条件
  2. 避免死锁:避免系统进入不安全状态(银行家算法)
  3. 死锁的检测和解锁:允许死锁发生,系统负责检测出死锁并解除。

2. 预防死锁

在运行时避免发生死锁

2.1 单个资源的银行家算法:

一个小城镇的银行家,他向一群客户分别承诺了一定的贷款额度,算法要做的是判断对请求的满足是否会进入不安全状态,如果是,就拒绝请求;否则予以分配。

上图 c 为不安全状态,因此算法会拒绝之前的请求,从而避免进入图 c 中的状态。

2.2 多个资源的银行家算法

上图中有五个进程,四个资源。左边的图表示已经分配的资源,右边的图表示还需要分配的资源。最右边的 E、P 以及 A 分别表示:总资源、已分配资源以及可用资源,注意这三个为向量,而不是具体数值,例如 A=(1020),表示 4 个资源分别还剩下 1/0/2/0。

检查一个状态是否安全的算法如下:

  • 查找右边的矩阵是否存在一行小于等于向量 A。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的。
  • 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 A 中。
  • 重复以上两步,直到所有进程都标记为终止,则状态时安全的。 如果一个状态不是安全的,需要拒绝进入这个状态。

3. 死锁的检测与恢复

不试图阻止死锁,而是当检测到死锁发生时,采取措施进行恢复。

3.1 每种类型一个资源的死锁检测

上图为资源分配图,其中方框表示资源,圆圈表示进程。资源指向进程表示该资源已经分配给该进程,进程指向资源表示进程请求获取该资源。

图 a 可以抽取出环,如图 b,它满足了环路等待条件,因此会发生死锁。

每种类型一个资源的死锁检测算法是通过检测有向图是否存在环来实现,从一个节点出发进行深度优先搜索,对访问过的节点进行标记,如果访问了已经标记的节点,就表示有向图存在环,也就是检测到死锁的发生。

3.2 每种类型多个资源的死锁检测

上图中,有三个进程四个资源,每个数据代表的含义如下:

  • E 向量:资源总量
  • A 向量:资源剩余量
  • C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量
  • R 矩阵:每个进程请求的资源数量

进程 P1 和 P2 所请求的资源都得不到满足,只有进程 P3 可以,让 P3 执行,之后释放 P3 拥有的资源,此时 A = (2 2 2 0)。P2 可以执行,执行后释放 P2 拥有的资源,A = (4 2 2 1) 。P1 也可以执行。所有进程都可以顺利执行,没有死锁。

算法总结如下:

每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。

  1. 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A。
  2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。
  3. 如果没有这样一个进程,算法终止。

3.3 死锁恢复

  • 利用抢占恢复
  • 利用回滚恢复
  • 通过杀死进程恢复

【操作系统基础】进程管理(三)进程同步与互斥相关推荐

  1. 操作系统:第二章 进程管理3 - 进程同步与互斥

    本文已收录至 Github(MD-Notes),若博客中有图片打不开,可以来我的 Github 仓库:https://github.com/HanquanHq/MD-Notes,涵盖了互联网大厂面试必 ...

  2. 操作系统之——进程管理:同步进程和进程互斥

    操作系统进程管理-同步和互斥 在看了操作系统关于进程管理中的同步互斥机制章节之后,甚是困惑,今天通过视频.网上博客资料学习之后,整理一下相关知识点. 进程管理 一.进程互斥 由于进程具有独立性和异步性 ...

  3. 计算机操作系统进程同步实验报告,操作系统-进程管理与进程同步-实验报告

    进程管理与进程同步实验报告 实验一.进程管理与进程同步 一.实验目的 了解进程管理的实现方法,理解和掌握处理进程同步问题的方法. 二.实验内容 实现银行家算法.进程调度过程的模拟.读者-写者问题的写者 ...

  4. 【操作系统】进程管理(二)

    [操作系统]进程管理(二) 一.前言 二.进程的基本概念 2.1 程序的顺序执行 2.2 程序的并发执行 2.3 进程的特征 2.4 进程的状态 2.5 进程控制块 三.进程控制 3.1 进程的创建 ...

  5. 【操作系统】进程管理(五)—— 信号量机制

    [操作系统]进程管理(五)-- 信号量机制 前言 一.信号量机制 信号量机制--整型信号量 信号量机制--记录型信号量 二.用信号量机制实现进程互斥.同步.前驱关系 信号量机制实现进程互斥 信号量机制 ...

  6. 操作系统之进程管理-翟一鸣-专题视频课程

    操作系统之进程管理-192人已学习 课程介绍         主要内容是程序的并发执行及进程的概念,进程的状态及其转换,进程的同步与互斥,进程通信与调度,进程死锁的概念及解决死锁的方法,线程的概念及其 ...

  7. 视频教程-操作系统之进程管理-操作系统

    操作系统之进程管理 1979年出生于甘肃省兰州市,2001年7月本科毕业于西北师范大学计算机科学与技术专业,同年于烟台大学计算机学院任教至今:期间于2006年获得上海交通大学软件工程硕士学位,现为学院 ...

  8. 操作系统笔记——进程管理

    操作系统笔记--进程管理 2. 进程管理 2.1 进程与线程 2.1.1 进程的引入 前趋图 程序的顺序执行 程序的并发执行 2.1.2 进程的定义及描述 进程的定义 进程的特征 进程和程序的关系 进 ...

  9. 操作系统之进程管理:11、用信号量机制实现进程同步、互斥、前驱关系

    11.用信号量机制实现进程同步.互斥.前驱关系 思维导图 用信号量机制实现进程同步 用信号量机制实现进程互斥 用信号量机制实现进程的前驱关系 思维导图 用信号量机制实现进程同步 先来看一下什么是进程同 ...

  10. 操作系统 实验一 进程管理与进程同步

    理解安全性算法和银行家算法的核心机制: 针对3类资源.5个进程的情况,设计相应的数据结构,分别表示每个进程占用各类资源的情况: 编程实现安全性算法函数,编制主函数,动态输入资源的占用情况,进程的资源申 ...

最新文章

  1. pip install Read timed out 超时问题解决
  2. win10 安装microsoft.net framework3.5
  3. RFID系统集成公司
  4. JDBC学习笔记03【JDBC事务管理、数据库连接池、JDBCTemplate】
  5. 拆轮子系列--RxJava理解(三)--observeOn
  6. 老司机请注意:POS机可盗刷ETC联名卡
  7. java html2text_将HTML转化为TEXT的Java类
  8. modelsim 10.7安装教程
  9. jsp html5 模板,JSP标准模板库
  10. 开源代码和框架专栏汇总
  11. python seo快排_流量贩子GoGo闯:SEO黑帽点击快排发包+Python应用软件编程技术
  12. 覆盖网络(Overlay Network)
  13. 什么是熔断、降级、限流
  14. 什么是算命里说的三奇命,天上三奇,地下三奇,人中三奇
  15. 重新安装anaconda的感想
  16. 基于TensorFlow的歌曲曲风变换
  17. 一个字符导致oracle安装出现ora-12154
  18. 周末和新润视频聊天了
  19. 软件测试宣传ppt,[软件测试ppt完整版.ppt
  20. Matlab—什么是nc文件,以及如何读取导入

热门文章

  1. 【年度总结】2022回首瞻望 | 2023大展宏“兔“
  2. C语言学习(三)数据-浮点类型
  3. wargame v2.1 Web Wrtteup By Assassin
  4. python3解两数之和
  5. Windows 11 手机诞生,还是双屏的?
  6. 【复盘1】政治+数学+英语+专业课
  7. 高级项目管理-4、项目范围、进度、成本、质量管理
  8. 推荐一个学习SQL的好网站
  9. java数组查找奇数_java – 在数组中对偶数和奇数进行排序
  10. 拼多多关键词搜索商品详情分析接口(分类ID搜索精准商品数据)代码对接教程