操作系统导论习题解答(30. Condition Variables)
Condition Variables
输出结果如下:
在多线程情况下我们可以尝试使用共享变量,可以但是效率非常低下:
问题来了:在多线程情况下,线程应该如何等待条件?
1. Definition and Routines
condition variable有两个操作:
wait() // 当一个线程希望被设置成sleep状态时被调用
signal() // 当一个线程改变了程序中的某值,希望从sleep状态中被唤醒时被调用
看一下POSIX:
注意:wait中有个参数mutex,wait责任是释放锁并使调用程序进入sleep状态;当线程唤醒时,它必须重新获取锁,然后才能返回到调用方。
上述代码有两种执行情况:
- parent线程创建child线程后继续执行thr_join(),由于done = 0,进入sleep状态等待child线程执行完毕,然后parent线程被唤醒,执行完毕。
- 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,继续进入等待状态。