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

Condition Variables

在这里插入图片描述
输出结果如下:
在这里插入图片描述
在多线程情况下我们可以尝试使用共享变量,可以但是效率非常低下
在这里插入图片描述
问题来了:在多线程情况下,线程应该如何等待条件?

1. Definition and Routines

condition variable有两个操作:

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

看一下POSIX:

在这里插入图片描述
注意:wait中有个参数mutex,wait责任是释放锁并使调用程序进入sleep状态;当线程唤醒时,它必须重新获取锁,然后才能返回到调用方。
在这里插入图片描述
上述代码有两种执行情况:

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

如果上述情况没有done这个condition variable会怎么样? 看看下面代码:
在这里插入图片描述
如果发生上述第2种情况,child线程发出signal后执行完毕(没有线程处于sleep状态),然后parent线程调用wait被卡住,没有线程会唤醒它。

如果没有加锁,会发生什么?看看如下代码:
在这里插入图片描述
如果parent线程创建child线程后继续执行,本来done = 0尝试进入sleep状态。但是这时刚巧发生了中断,开始执行child线程,改变done = 1并发出signal,但是没有线程在等待,因此无线程唤醒。然后转到parent线程执行时,它会永远进入sleep状态。

2. The Producer/Consumer (Bounded Buffer) Problem

在这里插入图片描述
上述代码可以在放入数据或获取数据时使用,但是对于同时进行这两个操作,那上述代码就会发生错误。
为了解决这个问题,设置共享缓冲区(shared buffer),有如下代码:
在这里插入图片描述

2.1 A Broken Solution

假设我们只有一个producer和一个consumer。进行如下第一次尝试:
在这里插入图片描述
上述代码对于一个producer和一个consumer而言有效,但是对于多个,有两个至关重要的问题:(1)producer唤醒Tc1,但在Tc1运行前,bounded buffer的状态已更改(由于Tc2
在这里插入图片描述

2.2 Better, But Still Broken: While, Not If

处理上述问题很简单,把if改成while。如下所示:
在这里插入图片描述
但是,这代码还是有问题(上述提到的第2个问题):(2)当两个线程(Tc2和Tp)都进入sleep状态后,该唤醒线程哪个线程?
在这里插入图片描述

2.3 The Single Buffer Producer/Consumer Solution

解决第二个问题的方法就是使用两个condition variable,能更好地标识出唤醒哪个sleep状态下的线程。
在这里插入图片描述

2.4 The Correct Producer/Consumer Solution

其实上述Figure 30.12中代码已经很好了,但是为了更好的并发性和高效性,我们还可以做一下优化。
在这里插入图片描述
在这里插入图片描述

3. Covering Conditions

再看一个例子。带着问题:当有多个sleep状态的线程时,哪个线程应该被唤醒?
在这里插入图片描述
考虑如下情况:假设没有多余字节可用,线程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  阅读(32)  评论(0编辑  收藏  举报