操作系统导论习题解答(30. Condition Variables)

Condition Variables

![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215180017580.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA,size_16,color_FFFFFF,t_70)
输出结果如下:
在这里插入图片描述
在多线程情况下我们可以尝试使用共享变量,可以但是效率非常低下
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215180348297.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA
,size_16,color_FFFFFF,t_70)
问题来了:在多线程情况下,线程应该如何等待条件?

1. Definition and Routines

condition variable有两个操作:

wait()		// 当一个线程希望被设置成sleep状态时被调用
signal()	// 当一个线程改变了程序中的某值,希望从sleep状态中被唤醒时被调用

看一下POSIX:

在这里插入图片描述
注意:wait中有个参数mutex,wait责任是释放锁并使调用程序进入sleep状态;当线程唤醒时,它必须重新获取锁,然后才能返回到调用方。
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215181932464.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA==,size_16,color_FFFFFF,t_70)
上述代码有两种执行情况:

  1. parent线程创建child线程后继续执行thr_join(),由于done = 0,进入sleep状态等待child线程执行完毕,然后parent线程被唤醒,执行完毕。
  2. parent线程创建child线程后,child线程直接开始执行,child线程执行完毕导致done = 1,然后parent线程不会进入sleep状态而是直接执行完毕。

如果上述情况没有done这个condition variable会怎么样? 看看下面代码:
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215183524632.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA==,size_16,color_FFFFFF,t_70)
如果发生上述第2种情况,child线程发出signal后执行完毕(没有线程处于sleep状态),然后parent线程调用wait被卡住,没有线程会唤醒它。

如果没有加锁,会发生什么?看看如下代码:
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215184904383.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA==,size_16,color_FFFFFF,t_70)
如果parent线程创建child线程后继续执行,本来done = 0尝试进入sleep状态。但是这时刚巧发生了中断,开始执行child线程,改变done = 1并发出signal,但是没有线程在等待,因此无线程唤醒。然后转到parent线程执行时,它会永远进入sleep状态。

2. The Producer/Consumer (Bounded Buffer) Problem

![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215190540551.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA,size_16,color_FFFFFF,t_70)
上述代码可以在放入数据或获取数据时使用,但是对于同时进行这两个操作,那上述代码就会发生错误。
为了解决这个问题,设置共享缓冲区(shared buffer),有如下代码:
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215191109256.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA
,size_16,color_FFFFFF,t_70)

2.1 A Broken Solution

假设我们只有一个producer和一个consumer。进行如下第一次尝试:
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215191702519.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA,size_16,color_FFFFFF,t_70)
上述代码对于一个producer和一个consumer而言有效,但是对于多个,有两个至关重要的问题:(1)producer唤醒Tc1,但在Tc1运行前,bounded buffer的状态已更改(由于Tc2
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215192414621.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA
,size_16,color_FFFFFF,t_70)

2.2 Better, But Still Broken: While, Not If

处理上述问题很简单,把if改成while。如下所示:
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215193023339.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA,size_16,color_FFFFFF,t_70)
但是,这代码还是有问题(上述提到的第2个问题):(2)当两个线程(Tc2和Tp)都进入sleep状态后,该唤醒线程哪个线程?
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215194333752.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA
,size_16,color_FFFFFF,t_70)

2.3 The Single Buffer Producer/Consumer Solution

解决第二个问题的方法就是使用两个condition variable,能更好地标识出唤醒哪个sleep状态下的线程。
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215195059135.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA==,size_16,color_FFFFFF,t_70)

2.4 The Correct Producer/Consumer Solution

其实上述Figure 30.12中代码已经很好了,但是为了更好的并发性和高效性,我们还可以做一下优化。
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215195846497.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA,size_16,color_FFFFFF,t_70)
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215195912959.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA
,size_16,color_FFFFFF,t_70)

3. Covering Conditions

再看一个例子。带着问题:当有多个sleep状态的线程时,哪个线程应该被唤醒?
![在这里插入图片描述]( https://img-blog.csdnimg.cn/20201215200234137.png?x-oss-process=image/watermark ,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MTcwMzE3NA==,size_16,color_FFFFFF,t_70)
考虑如下情况:假设没有多余字节可用,线程Ta调用allocate(100),然后线程Tb调用allocate(10),由于没有可用字节空间,Ta和Tb都进入sleep状态。接着Tc调用free(50),当Tc发出signal唤醒正在等待的线程时,它可能没有正确唤醒Tb(Ta由于需要100字节故仍在sleep状态)。
解决这一问题的方法就是唤醒所有等待的线程(增加性能开销)。不必要的线程会重新检查condition,继续进入等待状态。

posted @ 2022-10-14 19:09  astralcon  阅读(71)  评论(0)    收藏  举报