设为首页 加入收藏

TOP

优先级反转那些事儿(二)
2023-07-23 13:26:40 】 浏览:682
Tags:那些事
erInitiated; // 模块1 // if (j % 100 == 0) { // operation.qualityOfService = NSQualityOfServiceBackground; // } // 模块1 [operation addOperationWithBlock:^{ // 模块2 // qos_class_t oldQos = qos_class_self(); // pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0); // 模块2 NSTimeInterval start = CFAbsoluteTimeGetCurrent(); double sum = 0; for (int i = 0; i < 100000; ++i) { sum += sin(i) + cos(i) + sin(i*2) + cos(i*2); } start = CFAbsoluteTimeGetCurrent() - start; if (j % 100 == 0) { printf("%.8f\n", start * 1000); } // 模块2 // pthread_set_qos_class_self_np(oldQos, 0); // 模块2 }]; }

统计信息如下图所示

A B C
(注释模块1和模块2代码) (只打开模块1代码) (同时打开模块1和模块2代码)
11.8190561 94.70210189 15.04005137

可以看到

  1. 正常情况下,每个任务的平均耗时为:11.8190561;
  2. operation被设置为低优先级时,其耗时大幅度提升为:94.70210189;
  3. operation被设置为低优先级时,又在Block中手动恢复其原有的优先级,其耗时已经大幅度降低:15.04005137( 耗时比正常情况高,大家可以思考下为什么)

通过Demo可以发现,通过手动调整其优先级,低优先级任务的整体耗时得到大幅度的降低,这样在持有锁的情况下,可以减少对主线程的阻塞时间。

上线效果

图片

该问题的验证过程分为2个阶段:

  1. 第一个阶段如第1个红框所示,从36号开始在版本19.7上有较大幅度的下降,主要原因:堆栈中被等待的队列信息由QOS:BACKGROUND变为了com.apple.root.default-qos,队列的优先级从QOS_CLASS_BACKGROUND提升为QOS_CLASS_DEFAULT,相当于实施了方案一,使用了默认优先级。
  2. 第二个阶段如第2个红框所示,从424号在版本20.3上开始验证。目前看起来效果暂时不明显,推测一个主要原因是:demo中是把优先级从QOS_CLASS_BACKGROUND提升为QOS_CLASS_USER_INITIATED,而线上相当于把队列的优先级从默认的优先级QOS_CLASS_DEFAULT提升为QOS_CLASS_USER_INITIATED所以相对来说,线上的提升相对有限。
    1. QOS_CLASS_BACKGROUNDMach层级优先级数是4;
    2. QOS_CLASS_DEFAULTMach层级优先级数是31;
    3. QOS_CLASS_USER_INITIATEDMach层级优先级数是37;

深刻理解优先级反转

那么是否所有锁都需要像上文一样,手动提升持有锁的线程优先级?系统是否会自动调整线程的优先级?如果有这样的机制,是否可以覆盖所有的锁?要理解这些问题,需要深刻认识优先级反转。

什么是优先级反转?

优先级反转,是指某同步资源被较低优先级的进程/线程所拥有,较高优先级的进程/线程竞争该同步资源未获得该资源,而使得较高优先级进程/线程反而推迟被调度执行的现象。根据阻塞类型的不同,优先级反转又被分为Bounded priority inversionUnbounded priority inversion。这里借助 Introduction to RTOS - Solution to Part 11 (Priority Inversion) 的图进行示意。

Bounded priority inversion

如图所示,高优先级任务(Task H)被持有锁的低优先级任务(Task L)阻塞,由于阻塞的时间取决于低优先级任务在临界区的时间(持有锁的时间),所以被称为bounded priority inversion。只要Task L一直持有锁,Task H就会一直被阻塞,低优先级的任务运行在高优先级任务的前面,优先级被反转。

这里的任务也可以理解为线程

图片

Unbounded priority inversion

Task L持有锁的情况下,如果有一个中间优先级的任务(Task M)打断了Task L,前面的bounded就会变为unbounded,因为Task M只要抢占了Task LCPU,就可能会阻塞Task H任意多的时间(Task M可能不止1个)

图片

优先级反转常规解决思路

目前解决Unbounded priority inversion2种方法:一种被称作优先权极限(priority ceiling protocol),另一种被称作优先级继承(priority inheritance)。

Priority ceiling protocol

在优先权极限方案中,系统把每一个临界资源与1个极限优先权相关联。当1个任务进入临界区时,系统便把这个极限优先权传递给这个任务,使得这个任务的优先权最高;当这个任务退出临界区后,系统立即把它的优先权恢复正常,从而保证系统不会出现优先权反转的情况。该极限优先权的值是由所有需要该临界资源的任务的最大优先级来决定的。

如图所示,锁的极限优先权是3。当Task L持有锁的时候,它的优先级将会被提升到3,和Task H一样的优先级。这样就可以阻止Task M(优先级是2)的运行,直到Task LTask H不再需要该锁。

图片

Priority inheritance

在优先级继承方案中,大致原理是:高优先级任务在尝试获取锁的时候,如果该锁正好被低优先级任务持有,此时会临时把高优先级线程的优先级转移给拥有锁的低优先级线程,使低优先级线程能更快的执行并释放同步资源,释放同步资源后再恢复其原来的优先级。

图片

priority ceiling protocolpriority inheritance都会在释放锁的时候,恢复低优先级任务的优先级。同时要注意,以上2种方法只能阻止Unbounded priority inversion,而无法阻止Bounded priority inversionTask H必须等待Task L执行完毕才能执行,这个反转是无法避免的)。

可以通过以下几种发生来避免或者转移Bounded priority inversion

  1. 减少临界区的执行时间,减少Bounded priority inversion的反转耗时;
  2. 避免使用会阻塞高优先级任务的临界区资源;
  3. 专门使用一个队列来管理资源,避免使用锁。

优先级继承必须是可传递的。举个栗子:当T1阻塞在被T2持有的资源上,而T2又阻塞在T3持有的一个资源上。如果T1的优先级高于T2T3的优先级,T3必须通过T2继承T1的优先级。否则,如果另

首页 上一页 1 2 3 4 5 6 7 下一页 尾页 2/15/15
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇从 iOS App 启动速度看如何为基础.. 下一篇uniapp ios原生插件开发 (framewo..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目