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

一、结论:慎用 dispatch_semaphore 做线程同步

与 OSSpinlock类似,使用 dispatch_semaphore 容易造成优先级反转[1]:

  • 此 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使用官方文档[2]。

如果没有其他线程同步地等待此 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 是谁

参考资料

  • [1] https://twitter.com/pedantcoder/status/1229999590482444288


  • [2] QoS使用官方文档: https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html