《OSTEP》信号量(chap31)
〇、前言
本文是对《OSTEP》第三十一章的实践与总结。
一、信号量
以下各个版本都是一个生产者-消费者模型,基于信号量实现,并且逐渐完善。
1 |
|
运行结果:
1 |
|
可以看到在单线程下(一个生产者线程,一个消费者线程)运行得很好。
1 |
|
运行结果:
1 |
|
运行结果可能看不出什么,但是生产者产生的值会被覆盖。这个情况是怎么发生的呢?
假设两个生产者(Pa和Pb)几乎同时调用put()
。当Pa先运行,在f1行先加入第一条数据(fill=0),假设Pa在将fill计数器更新为1之前被中断,Pb开始运行,也在f1行给缓冲区的0位置加入一条数据,这意味着那里的老数据被覆盖。
因此,我们的解决思路是将 put()
修饰成一个临界区。
1 |
|
运行结果:
1 |
|
那如果继续增加一个消费者呢?
1 |
|
运行结果:
1 |
|
运行得非常好,有一个问题,会不会出现重复消费行为(把一个值消费两次)?看起来好像会。
但是记得 full 信号量,初始值为 0,这意味着每次只允许一个线程进行消费。这不仅和生产者达成了正确的执行序列,而且还间接得使得每次只能有一个线程在 get()
。
二、读写锁
以下是利用信号量来实现一个读写锁。
数据在有线程读时,不能写。这意味着第一个线程读的时候,直接将写者锁占有,最后一个读者将写锁释放。以下是代码:
1 |
|
运行结果:
1 |
|
可以顺利运行。
三、哲学家就餐问题
1 |
|
运行结果(这里是在 Linux 平台):
1 |
|
可以看到所有的哲学家都能顺利完成就餐。这里解决的核心思路就是,最后一个哲学家拿刀叉的顺序和别的哲学家不一样,这在理论上不会造成死锁。
试分析一下(p 代表哲学家,f 代表叉子),易知假设在 t 时刻是一个环形等待:
- p0 占据 f0 请求 f1;
- p1 占据 f1 请求 f2;
- p2 占据 f2 请求 f3;
- p3 占据 f3 请求 f0;
画出资源配置图之后,很容易求出它的可达性矩阵:
因此,可达性矩阵为:
$$
P = \left(
\begin{matrix}
1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 \
\end{matrix}
\right)
$$
强分图也就是:
$$
P\bigwedge P^T = \left(
\begin{matrix}
1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 \
1 & 1 & 1 & 1 \
\end{matrix}
\right)
$$
因此,在 t 时刻处于死锁状态,这与我们的经验相一致。
四、实现一个信号量
这里利用锁和条件变量来实现一个信号量。
1 |
|
这确实是相当优雅。