6.7.3 活锁

在某种情形下,轮询(忙等待)可用于进入临界区或存取资源。采用这一策略的主要原因是,相比所做的工作而言,互斥的时间很短而挂起等待的时间开销很大。考虑一个原语,通过该原语,调用进程测试一个互斥信号量,然后或者得到该信号量或者返回失败信息。如图2-26中的例子所示。

现在假设有一对进程使用两种资源,如图6-16所示。每个进程需要两种资源,它们利用轮询原语enter_region去尝试取得必要的锁,如果尝试失败,则该进程继续尝试。在图6-16中,如果进程A先运行并得到资源1,然后进程2运行并得到资源2,以后不管哪一个进程运行,都不会有任何进展,但是哪一个进程也没有被阻塞。结果是两个进程总是一再消耗完分配给它们的CPU配额,但是没有进展也没有阻塞。因此,没有出现死锁现象(因为没有进程阻塞),但是从现象上看好像死锁发生了,这就是活锁(livelock)。

阅读 ‧ 电子书库
图 6-16 忙等待可能导致活锁

活锁也经常出人意料地产生。在一些系统中,进程表中容纳的进程数决定了系统允许的最大进程数量,因此进程表属于有限的资源。如果由于进程表满了而导致一次fork运行失败,那么一个合理的方法是:该程序等待一段随机长的时间,然后再次尝试运行fork。

广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元

现在假设一个UNIX系统有100个进程槽,10个程序正在运行,每个程序需要创建12个(子)进程。在每个进程创建了9个进程后,10个源进程和90个新的进程就已经占满了进程表。10个源进程此时便进入了死锁——不停地进行分支循环和运行失败。发生这种情况的可能性是极小的,但是,这是可能发生的!我们是否应该放弃进程以及fork调用来消除这个问题呢?

限制打开文件的最大数量与限制索引节点表的大小的方式很相像,因此,当它被完全占用的时候,也会出现相似的问题。硬盘上的交换空间是另一个有限的资源。事实上,几乎操作系统中的每种表都代表了一种有限的资源。如果有n个进程,每个进程都申请了1/n的资源,然后每一个又试图申请更多的资源,这种情况下我们是不是应该禁掉所有的呢?也许这不是一个好主意。

大多数的操作系统(包括UNIX和Windows)都忽略了一个问题,即比起限制所有用户去使用一个进程、一个打开的文件或任意一种资源来说,大多数用户可能更愿意选择一次偶然的活锁(或者甚至是死锁)。如果这些问题能够免费消除,那就不会有争论。但问题是代价非常高,因而几乎都是给进程加上不便的限制来处理。因此我们面对的问题是从便捷性和正确性中做出取舍,以及一系列关于哪个更重要、对谁更重要的争论。

值得一提的是,一些人对饥饿(缺乏资源)和死锁并不作区分,因为在两种情况下都没有下一步操作了。还有些人认为它们从根本上不同,因为可以很轻易地编写一个进程,让它做某个操作n次,并且如果它们都失败了,再试试其他的就可以了。一个阻塞的进程就没有那样的选择了。