很多时候,当一个进程为了等待mutex而刚刚进入睡眠的时候,mutex已经被释放了,如果能在第一时间感知mutex被释放那是再好不过的了,解决该问题的方式就是用自旋忙等而不是阻塞等待,是这样吗?
关于竞态,当初由于不能忍受频繁的睡眠/唤醒而引入了自旋锁,然后又因为自旋锁在实时系统中会导致其它进程长时间延迟造成吞吐量下降而在实时系统中又恢复 了睡眠/唤醒,实时系统中的首要特性不是节省开销,而是保证运行,睡眠/唤醒机制保证了争抢锁的进程不会影响到别的进程从而保证了吞吐量不会被降低,但是 自旋锁用mutex实现的代价是客观存在的,它必须实现优先级继承或者优先级置顶以保证不会发生优先级逆转和死锁,因为现在自旋锁可以睡眠了,而内核又是 可以抢占的,一旦有高优先级的进程就绪,那么它就会抢占低优先级的进程,这是如果低优先级的进程正好持有那一把锁,那么就会被阻塞而不能运行(单cpu或 者进程/cpu绑定的情况),此时一个中间优先级的进程抢占了低优先级的进程,那么这会导致高优先级的进程的阻塞时间过于长,在实时系统中必须避免这种情 况,因此在实时系统内核中,比如linux内核中,就实现了优先级继承协议-PIP,协议的实现引入了大量的数据结构和算法,这就引入了管理开销,这些开 销会抵消掉一部分睡眠/唤醒带来的吞吐量的提高和其它进程延迟的降低,因此,下面的一杆秤就需要在这二者之间拨弄秤砣了。
传统自旋锁的实现保证了自旋锁的持有者不会被打断,这就可以保证即使高优先级的进程被破延迟也只是在自旋,而自旋时间内持有锁的进程会一直运行,而且运行 逻辑一直和这把锁有关,即使它释放了锁之后没有被等待者抢到,那么抢到锁的进程也不会做别的事(在ticket spin lock中这种混乱得到了改善),虽然延迟是有的,但是起码都是在围绕着锁作正经事而不会被别的进程打断,如果你真的在持有自旋锁的时候调用了一个 schedule,那么只能怪写代码的人了。因此传统的自旋锁不用任何开销就可以避免优先级逆转之类的令人难堪的局面,是的,没有任何开销,把抢占一关锁 一占完事,剩下的就尽情执行吧,不会被打扰,然而这样的话虽然避免了优先级逆转带来的争抢锁高优先级的进程延迟但是会引入不争抢锁的所有高优先级进程的延 迟,因为自旋锁简单的关闭了抢占(简单无开销)。因此传统的自旋锁和睡眠/唤醒机制都不适合实时系统,带有优先级继承协议的mutex机制实现的自旋锁是 可以的,但是管理优先级继承协议的数据结构和算法也够呛,这么多的但是降到底如何是好,现在有三种方案实现实时系统自旋锁,第一就是传统自旋锁,第二就是 传统的睡眠/唤醒机制,第三就是实现PIP的mutex机制,前两种都基本没有管理开销但是因为影响系统吞吐量和延迟,第二种还会引入睡眠/唤醒开销,这 导致这两种不能用,第三个方案由于不怎么影响系统延迟但是运行开销很大,运行开销有睡眠/唤醒的开销,管理复杂数据结构和算法的开销。因此,将这三种方案 的优势组合然后避免它们的劣势是最好的结果了,其实这三种方案正交化一下就是:自旋锁,睡眠/唤醒,PIP协议,其中PIP是不可省略的,因此选择自旋锁 的低开销和睡眠/唤醒的高吞吐量,因此结果就是自适应自旋锁,也就是说它会在某些情况下自旋而在另外一些情况下睡眠,这就是它的优势。那么在何种情况下自 旋呢?理想的情况就是自动学习,起初可能会很影响性能,毕竟要学习嘛,多次尝试自旋以后,会得到一些统计值,比如得到锁的平均延迟,平均自旋次数,n次自 旋内得到锁的次数,系统根据这些统计值决定下一次是自旋还是睡眠。然而这种实现合理吗?看似很合理啊,也很智能,几乎不用怎么配置就可以自动运行的很好, 但是想想看,这毕竟是在内核,内核不是秀算法的地方,智能的,复杂的算法还是在用户空间秀比较好,内核算法和数据结构的特征就是简单,高效,因此内核实现 的自适应锁还是要另外开辟新的方案。