多年使用锁的经验说明,我们很难驾轻就熟地使用锁。并发的管理本来就非常棘手,而许多使用方法都可能导致错误。本文将总结一些并发控制中容易导致错误的东西。
不明确的规则:
恰当的锁定模式需要清晰和明确的规则。当我们创建一个可被并行访问的对象时,应该同时定义用来控制访问的锁。锁定模式必须在一开始就安排好,否则其后的改进将会非常困难。先期的时间投入通常会在调试阶段获得收益。
在编写代码时肯定会遇到几个函数,它们均需要访问某个受特定锁保护的结构。这时。我们必须小心:如果某个获得锁的函数要调用其它同样试图获取这个锁的函数。我们的代码就会死锁。不论是信号量还是自旋锁,都不允许锁拥有者第二次获得这个锁;如果试图这么 做,系统将挂起。
为了让锁定正确工作,则不得不编写一些函数,这些函数假定调用者已经获取了相关的锁。通常,内部的静态函数可以通过这种方式编写。而提供给外部调用的函数则必须显式地处理锁定。在编写那些假定调用者已经处理了锁定的内部函数时,我们自己因该显式地说明这种假定。因为如果在几个月之后再回过头来看这些代码时,我们会发现很难记清在调用某个特定函数时是否需要拥有锁。
锁的顺序规则:
在使用大量锁的系统中,代码通常需要一次拥有多个锁。如果某种类型的计算必须使用两个不同的资源来完成,而每个资源都有自己的锁,则通常没有其他办法来同时获取这两个锁。
但是,拥有多个锁可能很危险。如果我们有两个锁,分别是lock1和lock2,而代码需要同时获取这两个锁,这时就有可能进入潜在的死锁。想象某个线程锁定了lock1,而其他线程锁定了lock2.这时每个线程都试图获得另外的那个锁,于是两个线程都将死锁。
对于这个问题的解决办法通常比较简单:在必须获取多个锁时,应该始终以相同的顺序获得。只要遵守这个约定,如上所述的那种死锁就可以避免。但是,下面的锁顺序规则说起来要比做起来容易。这类规则基本上不会出现在某个实际的系统中。通常。最好的办法是了解其他代码的做法。
有帮助的规则有两个:如果我们必须获得一个局部锁(比如一个设备锁),以及一个属于内核更中心位置的锁。则应该首先获取自己的局部锁。如果我们拥有信号量和自旋锁的组合,则必须首先获得信号量;在拥有自旋锁时调用down(可能导致休眠)是个严重的错误。当然最好的办法是避免出现需要多个锁的情况。