在并发队列使用信号量会可能会造成线程优先级反转

一、在iOS16 & XCode14上遇到 – 使用信号量造成线程优先级反转问题 提醒

经过查询资料,发现是在XCode14上增加了工具,比如 :

Thread Performance Checker (XCode14上默认开启的),这个工具会让APP在运行的时候,发现有例如线程优先级反转和非UI工作在主线程上运行等问题的时候,就会在XCode问题导航栏中提示该卡顿风险警告,可以帮助我们在开发初期就能发现并解决隐含的卡顿风险问题;这个不是崩溃,如果不想要,可以在 “Product -> Scheme – > Edit Scheme 的 Diagnostics 中去掉 Thread Performance Checker勾选”

XCode14还有其他一些新增加的工具类,可参考 iOS卡顿检测

二、关于线程优先级反转

优先级反转(Poiority Inversion) 指高优先级任务需要等待低优先级任务执行完成才能继续执行,这种情况下优先级被反转了。

举例:有三个线程分别为:A、B、C。优先级A > B > C,线程A和B处于挂起状态,等待某一事件发生,线程C正在运行,此时任务C开始使用共享资源Source。在使用Source时,线程A等待事件到来,线程A转为就绪态,因为线程A优先级比线程C高,所以线程A会立即执行。当线程A要使用共享资源Source时,由于共享资源Source正在被线程C使用,因此线程A被挂起,线程C开始运行。如果此时中等优先级线程B等待事件到来,则线程B转为就绪态。由于线程B优先级比线程C高,因此线程B开始运行,直到其运行完毕,线程C才开始运行。直到线程C释放共享资源Source后,线程A才得以执行。在这种情况下,优先级发生了翻转,线程B先于线程A运行。

三、优先级反转会造成什么后果

低优先级的任务比高优先级的任务先执行,导致任务的错乱,逻辑错乱;

可能造成系统崩溃;

死锁;优先级低的线程迟迟得不到调度,具有高优先级的线程不能执行,死锁;

四、怎么避免线程优先级反转

如果当前线程因等待某线程上正在进行的 操作如(block1)而受阻,而系统知道block1的所在的目标线程,系统会通过提高相关线程的优先级来解决优先级反转的问题 (如线程A在尝试获取共享资源而被挂起的期间内,将线程C的优先级提升到同线程A的优先级,等线程C处理结束,降回原优先级,这样能防止C被B抢占)。如果不知道block1所在的目标线程,则无法知道应该提高谁的优先级,也就无法解决反转的问题,如信号量。

五、使用信号量可能会造成线程优先级反转,且无法避免

QoS (Quality of Service),用来指示某任务或者队列的运行优先级;

1、记录了持有者的api都可以自动避免优先级反转,系统会通过提高相关线程的优先级来解决优先级反转的问题,如 dispatch_sync, 如果系统不知道持有者所在的线程,则无法知道应该提高谁的优先级,也就无法解决反转问题。

2、慎用dispatch_semaphore 做线程同步

dispatch_semaphore 容易造成优先级反转,因为api没有记录是哪个线程持有了信号量,所以有高优先级的线程在等待锁的时候,内核无法知道该提高那个线程的优先级(QoS);

3、dispatch_semaphore 不能避免优先级反转的原因

在调用dispatch_semaphore_wait() 的时候,系统不知道哪个线程会调用 dispatch_semaphore_signal()方法,系统无法知道owner信息,无法调整优先级。dispatch_group 和semaphore类似,在调用enter()方法的时候,无法预知谁会leave(),所以系统也不知道owner信息

参考资料:

优先级反转

优先级反转那些事儿

Diagnosing Performance issues early

dispatch_semaphore 会造成优先级反转,慎用!

作者:京东零售孙巧巧