DispatchQueue 是如何工作的? (特别是多线程)

How does a DispatchQueue work? (specifically multithreading)

我不了解 DispatchQueue 的工作原理,想了解更多有关它们如何实现基本排队论要求的信息。我尝试使用以下方法检查队列:

dump(DispatchQueue.global())

这给出了这个输出:

- <OS_dispatch_queue_global: com.apple.root.default-qos[0x10c041f00] = { xref = -2147483648, ref = -2147483648, sref = 1, target = [0x0], width = 0xfff, state = 0x0060000000000000, in-barrier}> #0
  - super: OS_dispatch_queue
    - super: OS_dispatch_object
      - super: OS_object
        - super: NSObject

我查到标签是com.apple.root.default-qos,这个是Apple里指定的docs,class是封装好的OS_dispatch_queue_global。我知道 qos 在队列本身上是可查询的,这也很有意义。我认为宽度只是指分配的内存大小。

我不明白 xrefrefsref 的相关性,我认为它们是队列的内部 ID,但我不确定。我认为它们与基本队列概念相关(想到了多线程),但更详细地研究这一点会很棒。

autoreleaseFrequency 是否隐藏在此调试描述中?另外,in-barrier = 0 是什么意思?我尝试创建一个自定义队列,它被替换为 in-flight = 0.. 对此也很困惑。

关于这些未记录的变量如何与排队论相关的任何想法?我认为这些是 API 的未记录的内部结构,因此任何受过教育和合理的解释都可以!

谢谢。

为什么要问这个?

这是一个关于 grand-central-dispatch 内部结构的相当广泛的问题。我很难理解 dumped 的输出,因为 GCD 的原始 WWDC '10 视频和幻灯片不再 public。我也不知道开源 libdispatch 存储库(感谢 Rob)。这不一定是个问题,但是 SO 上没有相关的 QA 来详细解释这个主题。

为什么选择 GCD?

根据 WWDC '10 GCD 记录(感谢 Rob),API 背后的主要思想是简化与使用 #selector API 进行多线程相关的样板。

GCD

的好处

Apple 发布了一个新的基于 block 的 API 而不是使用函数指针,以启用类型安全代码,如果块具有错误的类型签名,则不会崩溃。在函数参数、局部变量和 @property 声明中使用时,使用 typedef 还可以使代码更清晰。队列允许您将代码和某些状态捕获为一大块数据,这些数据在后台自动进行管理、排队和执行。

同一个会话提到了 GCD 如何在幕后管理低级线程。它在需要执行线程时将块排入队列以在线程上执行,然后在不再引用它们时释放这些线程(准确地说是 PThreads)。 GCD 自动管理线程并且不会公开此 API - 当 DispatchWorkItem 出队时 GCD 创建一个线程供此块执行。

performSelector

的缺点

performSelector:onThread:withObject:waitUntilDone: 有许多缺点,表明对并发、等待、同步等现代挑战的设计不佳。在 func 中切换线程时会导致厄运金字塔。此外,NSObject.performSelector 线程方法系列不灵活且有限:

  1. 没有针对特定线程上的并发、初始非活动或同步进行优化的选项。不像 GCD.
  2. 只能将 选择器 分派到新线程(糟糕)。
  3. 给定函数的大量线程会导致代码混乱(厄运金字塔)。
  4. 不支持无限制排队(在iOS4公布GCD的时候)NSOperationAPI。 NSOperations 是一个高级的、冗长的 API,在 iOS 4.
  5. 许多与未处理的无效 Selector 错误(类型安全)相关的错误。

DispatchQueue 内部结构

我相信 xrefrefsref 是管理自动引用计数的引用计数的内部寄存器。 GCD 在大多数情况下会在需要时调用 dispatch_retaindispatch_release,因此我们无需担心在执行完所有块后释放队列。但是,在某些情况下,开发人员可以手动调用 retainrelease 以确保队列即使在不直接使用时也能保留。当在具有正引用计数的队列上调用 release 时,这些寄存器允许 libDispatch 崩溃,以便更好地处理错误。

当调用带有 DispatchQueue.global().async 或类似的块时,我相信这会增加该队列的引用计数(xrefref)。

问题中的变量没有明确记录,但据我所知:

  • xref统计外部引用的次数一般DispatchQueue.
  • ref统计总引用次数DispatchQueue.
  • sref 计算对 API serial/concurrent/runloop 队列、源和 mach 通道的引用数(这些需要不同地跟踪,因为它们使用不同的类型表示)。

in-barrier 看起来像一个内部状态标志 (DispatchWorkItemFlag),用于跟踪是否应安排提交到并发队列的新工作项。只有在屏障工作项完成后,队列 returns 才能安排在屏障之后提交的工作项。 in-flight表示目前没有有效的障碍。

state 也没有明确记录,但我假设指向内存,块可以在其中访问块调度范围内的变量。