🌧 南京天气转凉了…
drink more hot water…☕️
上一篇文章地址链接: 【操作系统⑦】——信号量与PV操作(上)【生产者消费者经典问题】.
下一篇文章地址链接: 【操作系统⑨】——进程间通信的概述【kill() signal() shmget() shmat() 实例 共享存储通信 消息通信 管道通信 】.
期末考试总复习——地址链接:《操作系统期末总复习——绝地求生版》.
一、哲学家进餐问题
● 问题描述:有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图使用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,同时放下筷子又继续思考。
● 若按照 【操作系统⑦】——信号量与PV操作(上)【生产者消费者经典问题】的思路,如果我们用记录型信号量来处理:
/* 基于 C 语言写的伪代码 */
semaphore chopstick[5] = {1, 1, 1, 1, 1}; // 筷子是资源, 5 支筷子分别设置为初始值为 1 的互斥信号量
while(true)
{
"第 i 个哲学家的进程 Pi():" // i = 1, 2, 3, ..., a
while(true)
{
wait(chopstick[i]); // 拿起左手的筷子
wait(chopstick[(i+1) % 5]); // 拿起右手的筷子
eat();
signal(chopstick[i]); // 放下左手的筷子
signal(chopstick[(i+1) % 5]); // 放下右手的筷子
think();
}
}
◆ 代码说明:wait(…) 就是 P 操作。signal(…) 就是 V 操作。
● 这段代码看似能解决问题,且好像没问题,但仔细分析就会发现:若五位哲学家每个人都同时饥饿且拿起了左边的筷子,这使得五个信号量 chopstick 均为 0。当他们再试图去拿起右边的筷子时,都将因无筷子而无限期地等待下去,即可能会引起死锁。
如何解决这个问题呢?
① 方法一:至多只允许四位哲学家同时去拿左筷子,最终能保证至少有一位哲学家能进餐,并在用完后释放两只筷子供他人使用。
② 方法二:仅当哲学家的左右手筷子都拿起时才允许进餐。
③ 方法三:规定奇数号哲学家先拿左筷子再拿右筷子,而偶数号哲学家相反。
方法一和三都可以通过算法设计(同时配合前面的记录型变量也能解决问题)来实现。但方法二有所不同(涉及到本文章所讲的内容), 我们就来研究方法二,接下来就要引入 AND型信号量 和 信号量集机制。
二、AND型信号量
● AND型信号量:指同时需要多个资源且每种占用一个资源时的信号量操作。当一段处理代码需要同时获取两个或多个临界资源时,就可能出现由于各进程分别获得部分临界资源并等待其余的临界资源的局面。各进程都会 “各不相让”,从而出现死锁。
● AND型信号量的基本思想:在一个原语中申请整段代码需要的多个临界资源,要么全部分配给它,要么一个都不分配给它。
● AND型信号量 P 原语为 Swait(Simultaneous Wait),V 原语为 Ssignal(Simultaneous Signal)。两者定义如下:
/* 基于 C 语言写的伪代码 */
"Swait(s_1, s_2, ..., s_n) 操作"
semaphore s_1, s_2, ..., s_n 进行初始化;
procedure Swait(s_1, s_2, ..., s_n)
{
if (s_1 >= 1 and s_2 >= 1 and ... and s_n >= 1)
{
for( int i = 1; i<= n ;i++ )
s_i = s_i - 1;
}
else
{
blockProcessAndResetPC(s_firstless); // 阻塞
}
}
"Ssignal(s_1, s_2, ..., s_n) 操作"
semaphore s_1, s_2, ..., s_n 进行初始化;
procedure Ssignal(s_1, s_2, ..., s_n)
{
for( int i = 1; i<= n ;i++ )
s_i = s_i + 1;
wakeupProcess(s_i); // 唤醒
}
◆ 代码说明:
① Swait 操作表示的是获取资源。s1 到 sn 都表示所需资源。资源数都大于 1 时,可以对每个资源进行申请,分配好资源之后跳出循环,Swait 操作结束。如果其中某个资源 si 得不到满足,会执行 else 中的内容:把进程放进 si 关联的阻塞队列中,然后程序计数器会把指针移向 Swait 操作开始。(( wait 操作是原语,遵循要执行都执行,执行不了就从头重新执行)
② Ssignal 操作表示的是释放资源。它会把 s1 到 sn 全部资源释放,并且把 s1 到 sn 关联的阻塞队列全部置空,阻塞队列中的进程直接调度到就绪队列中。
③ 在 Swait 时,各个信号量的次序并不重要,虽然会影响进程归哪个阻塞队列,但是因为要么是对资源的全部分配、要么是不分配,所以总是有进程获得全部资源并在推进之后释放资源,因此不会死锁。
● 用 AND型信号量 来解决一下哲学家用餐问题:【没有死锁】
/* 基于 C 语言写的伪代码 */
semaphore chopstick chopstick[5] = {1, 1, 1, 1, 1};
do
{
think();
Sswait(chopstick[i], chopstick[(i+1) % 5]); // 左右手的筷子能左右拿起才行
eat();
Ssignal(chopstick[i], chopstick[(i+1) % 5]); // 同时放下左右手的筷子
}while(true)
● AND型信号量满足了 “多种资源,数量为1” 的使用情景,但是实际上还会有多种资源数量不固定的情景,AND型信号量显然处理不了这种情况的进程调度。为了解决多资源多数量的情况,就出现了信号量集。
三、信号量集
● 信号量集:指同时需要多种资源、每种占用的数目不同、且可分配的资源还存在一个临界值时的信号量处理。由于—次需要 n 个某类临界资源,因此如果通过 n 次 wait 操作申请这 n 个临界资源,操作效率很低,并可能出现死锁。
● 信号量集的基本思路:在AND型信号量集的基础上进行扩充,在一次原语操作中完成所有的资源申请。进程对信号量 si 的测试值为 ti (表示信号量的判断条件,要求 si > ti 即当资源数量高于 ti 时才分配;低于 ti 时,便不予分配),占用值为 di(表示资源的申请量,即 s1 = si - di)。对应的 P、V 原语格式为:
/* 基于 C 语言写的伪代码 */
semaphore s_1, s_2, ..., s_n 进行初始化;
"Swait(s_1, t_1, d_1, ..., s_n, t_n, d_n) 操作"
procedure Swait(s_1, t_1, d_1, ..., s_n, t_n, d_n)
{
if (s_1 >= t_1 and s_2 >= t_2 and ... and s_n >= t_n)
{
for( int i = 1; i<= n ;i++ )
s_i = s_i - d_i;
}
else
{
blockProcessAndResetPC(s_firstless); // 阻塞
}
}
"Ssignal(s_1, d_1, ..., s_n, d_n) 操作"
procedure Ssignal(s_1, d_1, ..., s_n, d_n)
{
for( int i = 1; i<= n ;i++ )
s_i = s_i + d_i;
wakeupProcess(s_i); // 唤醒
}
● 一般 “信号量集” 可以用于各种情况的资源分配和释放。下面是几种特殊的情况:
① Swait(s,t,d) 表示每次申请 d 个资源,当资源数量少于 t 个时,便不予分配。
② Swait(s,1,1) 表示互斥信号量。
③ Swait(s,1,0) 可作为一个可控开关 (当 s ≥ 1 时,允许多个进程进入临界区;当 s = 0 时禁止任何进程进入临界区) 。
● 用信号量集改写 “读/写者问题”:(记录型信号量的做法在上一篇文章:【操作系统⑦】——信号量与PV操作(上).)
● 问题描述:有两组并发进程,包括众多读者和众多写者,共享一个文件,要求:
(1)允许多个读者同时对文件进行读操作。
(2)只允许一个写者对文件进行写操作。
(3)任何写者在完成写操作前不允许其他读者或写者工作。
(4)写者在执行写操作前,应让已有的写者和读者全部退出。
/* 基于 C 语言写的伪代码 */
int RN; // 读者数量
semaphore L = RN, mx = 1;
void reader()
{
while(true)
{
Swait(L, 1, 1);
Swait(mx, 1, 0); // 当 mx ≥ 1 时,允许多个进程进入临界区;当 mx = 0 时禁止任何进程进入临界区
// 读者进来也会剥夺 mx 信号量
...
read();
...
Ssignal(L, 1);
}
}
void writer()
{
while(true)
{
Swait(mx, 1, 1); // 或者用 Swait(L, RN, 0)
...
write();
...
Ssignal(mx, 1);
}
}
void main()
{
reader();
writer();
}
四、参考附录:
[1] 《操作系统A》
上课用的慕课
链接: https://www.icourse163.org/course/NJUPT-1003219004?from=searchPage.
[2] 《操作系统教程》
上课用的教材
[3] 进程的同步互斥(九)
链接: https://blog.csdn.net/essity/article/details/85289816.
基于Linux写的
[4] 《信号量》
链接: https://blog.csdn.net/huangweiqing80/article/details/83038154.
基于Linux写的
上一篇文章地址链接: 【操作系统⑦】——信号量与PV操作(上)【生产者消费者经典问题】.
下一篇文章地址链接: 【操作系统⑨】——进程间通信的概述【kill() signal() shmget() shmat() 实例 共享存储通信 消息通信 管道通信 】.
期末考试总复习——地址链接:《操作系统期末总复习——绝地求生版》.
操作系统系列文章正在更新中… ⭐️ ⭐️