线程调度的问题:Lock Convoy(锁封护)与Priority Inversion(优先级反转)
Lock Convoy(锁封护)
[1]Lock Convoy是在多线程并发环境下由于锁的使用而引起的性能退化问题。当多个相同优先级的线程频繁地争抢同一个锁时可能会引起lock convoy问题,一般而言,lock convoy并不会像deadlock或livelock那样造成应用逻辑停止不前,相反地,遭受lock convoy的系统或应用程序仍然往前运行,但是,由于线程们频繁地争抢锁而导致过多的线程环境切换,从而使得系统的运行效率大为降低,而且,若存在同等优先级下不参与锁争抢的线程,则它们可以获得相对较多的处理器资源,从而造成系统调度的不公平性。
Lock Convoy描述的是这样一种情况,假设有ABC三个线程在同一个时间片内多次取锁,在不考虑优先级的情况下(假设调度规则为平均时间),则可能发生当A执行1/3的时间片后被调度进程中断,转而执行B,B也执行了1/3的时间片后被调度进程中断,执行C。这样三个线程总是没执行完时间片就被打断。(在现代操作系统中,只要一个线程获取了一个时间片,那么在时间片内它就不能被重新执行),导致线程频繁切换,而降低系统的执行效率。
除了引起调度粒度变小以外,lock convoy的另一个问题是造成调度器的时间分配不公平。假设另有一个线程X也是在同等的优先级上运行,但没有参与锁竞争。于是,在每一轮的锁竞争过程中,线程X都有机会被分配一次完整的时间片,于是,这些竞争的线程在一轮中获得1/3时间片,而非竞争的线程可以获得完整的时间片。当然,你可以说这种不公平是由于它们抢锁而引起的,但从时间分配比例而言,参与竞争与不参与竞争的线程是不公平的。下图说明了线程X和A、B、C之间的执行时间差异。
由以上描述可以看出,Lock convoy的存在条件是,参与竞争的线程频繁地获取锁,锁被一个线程释放以后其所有权便落到了另一个线程的手里。在操作系统中,相同优先级的线程按照FIFO的顺序被调度和执行,竞争同一个锁的线程也按照FIFO的顺序被依次成功地获取到锁。这些条件在现代操作系统中都能被满足,包括Windows。
一种合理的缓解lock convoy的方案,要求在每个线程获取锁的时候先尝试(try),如果尝试多次仍不成功,再阻塞。
Priority inversion(优先级反转)
高优先级任务需要等待低优先级任务释放资源,而低优先级任务又正在等待中等优先级任务的现象,叫做优先级反转。
(注意: 此时高优先级任务和中等优先级任务之间没有任何共享资源但执行顺序却发生了倒置,这种情况才称为优先级反转;而高优先级任务因为等待低优先级任务释放资源而阻塞的情况则不称为优先级反转。)
图片含义:当低优先级在运行时被高优先级打断,但高优先级所使用的资源被低优先级所占用而阻塞,此时中优先级打断低优先级任务,低优先级任务发生了阻塞,而此时高优先级和中优先级没有任何资源共享,却发生了优先级反转。
两种经典的防止反转的方法:
1. (Priority inheritance,优先级继承):继承现有被阻塞任务的最高优先级作为其优先级,任务退出临界区,恢复初始优先级。 在上述例子中当高优先级任务需要等待低优先级任务释放资源而阻塞时,就将低优先级任务的优先级升为高优先级任务的优先级,当它退出临界区后就将其优先级恢复为初始优先级。
2. (Priority ceilings,设置优先级上限):设置优先级上限是指将申请(占有)某资源的任务的优先级提升到可能访问该资源的所有任务中最高优先级任务的优先级。如上面的例子,如果给低优先级线程抬高到最高优先级,那么中级优先级程序就不会被调度,不会发生优先级反转。