更多精彩内容,请关注作者微信公众号:码工笔记,欢迎留言交流。
一、结论:慎用 dispatch_semaphore 做线程同步
与 OSSpinlock类似,使用 dispatch_semaphore 容易造成优先级反转:
-
此 API 没有记录当前持有信号量的线程,所以有高优先级的线程在等待锁时,内核无法知道该提高哪个线程的调试优先级(QoS)
-
如果锁持有者优先级比其他线程低,高优先级的等待线程将一直等待
高效率的线程同步有两个关键点:
- 不忙等
- 记录持有者
自旋锁是两点都不符合,dispatch_semaphore 是只符合不忙等。
二、原理说明
下面先介绍一下 iOS 平台上的 QoS 概念和优先级反转避免机制,最后再说明为什么 dispatch_semaphore 不能避免优先级反转。
1. QoS 传递
QoS(Quality of Service),用来指示某任务或队列的运行优先级。
-
iOS 系统主要使用以下两种机制来在不同线程(或 queue)间传递 QoS:
-
机制1:dispatch_async
-
机制2:基于 XPC 的进程间通信(IPC)
-
-
系统的 QoS 传递规则比较复杂,主要参考以下信息:
-
当前线程的 QoS
-
如果是使用 dispatch_block_create() 方法生成的 dispatch_block,则考虑生成 block 时所调用的参数
-
dispatch_async 或 IPC 的目标 queue 或线程的 QoS
调度程序会根据这些信息决定 block 以什么优先级运行。具体用法请参见QoS使用官方文档。
-
如果没有其他线程同步地等待此 block,则 block 就按上面所说的优先级来运行。
如果出现了线程间同步等待的情况,则调度程序会根据情况调整线程的运行优先级。
2. 优先级反转及避免
-
优先级反转:当前线程在同步地等待某个线程(线程1)完成某项操作,而当前线程的优先级比线程1的优先级高。
-
优先级反转避免机制(Priority inversion avoidance):如果当前线程因等待某线程(线程1)上正在进行的操作(如 block1)而受阻,而系统知道 block1 所在的目标线程(owner),系统会通过提高相关线程的优先级来解决优先级反转的问题。
-
如果系统不知道 block1 所在目标线程,则无法知道应该提高谁的优先级,也就无法解决反转问题
-
记录了持有者信息(owner)的系统 API 如下:
-
pthread mutex、os_unfair_lock、以及基于这二者实现的其他上层 API
-
dispatch_once 的实现是基于 os_unfair_lock 的
-
NSLock、NSRecursiveLock、@synchronized 等的实现是基于 pthread mutex 的
-
-
dispatch_sync()
-
xpc_connection_send_with_message_sync()
使用以上这些 API 能够在发生优先级反转时使系统启用优先级反转避免机制。
-
-
3. dispatch_semaphroe 不能避免优先级反转的原因
-
semaphore 不是一个异步方法,所以它没有 QoS 的概念
-
在调用 dispatch_semaphore_wait() 时,系统不知道哪个线程会调用 dispatch_semaphore_signal() 方法,所以系统无法知道 owner 信息
dispatch_group 跟 semaphore 类似,在调用 enter() 方法时,无法预知谁会调用 leave(),所以系统也无法知道其 owner 是谁