【iOS】优先级反转
前言
什么是优先级反转呢?
优先级反转指的是某同步资源被较低优先级线程/进程拥有之后,其他较高优先级的线程/进程竞争未获得该资源,使得较高优先级的进程/线程被阻塞,根据阻塞类型的不同可以分为两类,如下:
| 类型 | 中文理解 | 核心区别 |
|---|---|---|
| Bounded Priority Inversion | 有界优先级反转 | 高优先级线程会被低优先级线程阻塞,但阻塞时间有明确上限 |
| Unbounded Priority Inversion | 无界优先级反转 | 高优先级线程被阻塞的时间无法确定,理论上可能无限长 |
优先级反转的后果:
- 低优先级的任务比高优先的任务先执行导致任务的错乱。
- 可能造成系统崩溃
- 产生死锁,低优先级迟迟无法被调度执行
两种类型
Bounded Priority Inversion
高优先级线程虽然被低优先级线程阻塞了,但这个阻塞时间是可预测、可控制、有上限的。阻塞时间取决于低优先级任务在临界区的时间,即持有锁的时间。
所以只要低优先级的线程一直持有锁,那么高优先级的线程就会一致被阻塞,低优先级的任务就会排在高优先级人物的前面,优先级被反转。
只要低优先级的线程能够运行并释放锁,那么优先级反转就会结束。
Unbounded priority inversion
高优先级线程等待低优先级线程释放资源,但是低优先级线程可能长期不会得到执行的机会,导致高优先级线程等待的时间没有明确上限。
但是实际实践中,我们会发现中间优先级线程不会一致抢占CPU,这是因为普通用户态线程默认不会严格抢占,优先级只是一个建议而不是强制的。线程的实际调度还受到系统负载、CPU核心数、线程创建顺序等的影响。
解决办法
目前解决无界优先级反转有两种方法:
- 优先权极限
- 优先级继承
优先权极限
系统将每一个临界资源与一个极限优先权相关联,当1个任务进入临界区时,系统便会将这个极限优先权传递给这个任务,使得这个任务的优先级最高,当任务退出临界区之后,系统将其优先权恢复正常。这个极限优先权的值是由所有需要该临界资源的任务的最大优先级来决定的。
优先级继承
大致的原理就是当高优先级任务在尝试获取锁的时候,如果该锁正好被低优先级任务持有,此时会临时的将高优先级线程的优先级转移给拥有锁的低优先级线程,使得低优先级线程能更快的执行并释放同步资源,释放完资源之后恢复为原来的优先级。
这两种方法只能阻止无界优先级反转而不能阻止有界优先级反转。(有界优先级反转必须等待低优先级线程执行完毕以后才能执行,这个反转是无法避免的)
我们还需要注意的是,优先级继承是可以传递的,举个例子来说:当线程1阻塞在被线程2持有的资源上,而线程2又被阻塞在线程3持有的资源上,瑞国线程1的优先级高于线程2和线程3的优先级,那么线程3必须通过线程2继承线程1的优先级。不然,如果有一个线程4的优先级高于线程2和线程3但是小于线程1的优先级,就会抢占线程3的执行,引发相对于线程1的优先级反转。所以线程所继承的优先级必须是直接或者间接阻塞的线程的最高优先级。
总结
- 什么是优先级反转?
优先级反转指的是一个低优先级的任务持有了一个共享资源的锁,而高优先级的任务获取锁的时候被阻塞,此时中间优先级的任务运行并抢占了CPU执行,导致高优先级的任务被反转到中间优先级的任务之后运行。
- iOS中如何避免或解决优先级反转问题?
- 优先级继承:确保持有锁的低优先级任务提升到高优先级任务的级别,知道锁被释放,然后恢复到原来的级别。(记录了锁的持有者的api都可以自动避免优先级的反转,系统会通过提高线程的优先级来解决反转问题,如dispatch_sync)
- 避免使用信号量机制做线程同步,无法区分是哪个线程持有了信号量
- 使用合适的锁机制,选择NSlock、NSRecursiceLock等,避免长时间持有锁
- 避免锁竞争:减少共享资源的使用和锁的力度,避免长时间的锁的竞争
