DispatchQueue.global()默认qos是userInitiated?

fra*_*ank 1 queue grand-central-dispatch ios swift

我写一个演示

let queue = DispatchQueue.global()
queue.async {
    let group = DispatchGroup()
    group.enter()
    DispatchQueue.global().asyncAfter(deadline: DispatchTime.now(), qos: .default) {
        //do sth in default queue
        group.leave()
    }
    group.wait()
    DispatchQueue.main.async { [weak self] in
        //do sth in main queue
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我收到线程性能检查警告

以 QOS_CLASS_USER_INITIATED 运行的线程正在等待以 QOS_CLASS_DEFAULT 运行的较低 QoS 线程。研究避免优先级倒置的方法

我尝试打印它显示的外部队列的 qos

(lldb) print queue.qos
(Dispatch.DispatchQoS) $R0 = {
  qosClass = unspecified   //I think this should be default.
  relativePriority = 0
}
Run Code Online (Sandbox Code Playgroud)

苹果文档错了?

class func global(qos: DispatchQoS.QoSClass = .default) -> DispatchQueue
Run Code Online (Sandbox Code Playgroud)

Rob*_*Rob 6

你的问题的标题说,

\n
\n

DispatchQueue.global()默认qos是userInitiated

\n
\n

考虑:

\n
examineQoS(qos: .default)         // original QoS: default;          queue QoS: DispatchQoS(qosClass: .unspecified,     relativePriority: 0); thread.QoS: .userInitiated   !!! default global queue is `.unspecified` and thread is `.userInitiated`\nexamineQoS(qos: .userInteractive) // original QoS: userInteractive;  queue QoS: DispatchQoS(qosClass: .userInteractive, relativePriority: 0); thread.QoS: .userInteractive\nexamineQoS(qos: .userInitiated)   // original QoS: userInitiated;    queue QoS: DispatchQoS(qosClass: .userInitiated,   relativePriority: 0); thread.QoS: .userInitiated\nexamineQoS(qos: .utility)         // original QoS: utility;          queue QoS: DispatchQoS(qosClass: .utility,         relativePriority: 0); thread.QoS: .utility\nexamineQoS(qos: .background)      // original QoS: background;       queue QoS: DispatchQoS(qosClass: .background,      relativePriority: 0); thread.QoS: .background\n\nfunc examineQoS(qos: DispatchQoS.QoSClass) {\n    let queue = DispatchQueue.global(qos: qos)\n    queue.async {\n        print("original QoS:", qos, "; queue QoS:", queue.qos, "; thread.QoS:", Thread.current.qualityOfService)\n    }\n}\n\nextension QualityOfService: CustomStringConvertible {\n    public var description: String {\n        switch self {\n        case .userInteractive: return ".userInteractive"\n        case .userInitiated:   return ".userInitiated"\n        case .utility:         return ".utility"\n        case .background:      return ".background"\n        case .default:         return ".default"\n        @unknown default:      return "@unknown default"\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,global()(或global(qos: .default)) 实际上是创建一个 QoS 为 的队列.unspecified,因此其工作线程将继承调用者(即主队列.userInitiated)的 QoS。

\n
\n

好的,让\xe2\x80\x99s 在代码片段中执行相同的日志记录:

\n
let outerQueue = DispatchQueue.global(qos: .default)\nouterQueue.async {\n    print("outerQueue:", outerQueue.qos, "; thread:", Thread.current.qualityOfService)\n\n    // outerQueue: DispatchQoS(qosClass: .unspecified, relativePriority: 0) ; thread: .userInitiated\n\n    let group = DispatchGroup()\n    group.enter()\n    let innerQueue = DispatchQueue.global()\n    innerQueue.asyncAfter(deadline: DispatchTime.now(), qos: .default) {\n        print("innerQueue:", innerQueue.qos, "; thread:", Thread.current.qualityOfService)\n\n        // innerQueue: DispatchQoS(qosClass: .unspecified, relativePriority: 0) ; thread: .default\n\n        //do sth in default queue\n        group.leave()\n    }\n    group.wait()\n    DispatchQueue.main.async { [weak self] in\n        self?.doSomething()\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

因此,外部队列(从主队列调度的队列)获取了 QoS 的工作线程.userInitiated,但内部队列(从外部队列调度的队列)使用了 QoS 的工作线程.default(因为您.defaultasyncAfter调用中明确指定了)。所以 \xe2\x80\x9cuser-initated\xe2\x80\x9d 线程正在等待 \xe2\x80\x9cdefault\xe2\x80\x9d 线程:优先级反转。

\n
\n

这就引出了一个问题:如何避免这种优先级倒置。

\n
    \n
  1. 显然,可以为两个队列指定显式 QoS,确保不会有高 QoS 线程等待低 QoS 线程。

    \n
  2. \n
  3. 您可以从 中删除该qos参数asyncAfter,内部队列将从调用者那里继承 QoS,并且不会出现优先级反转。

    \n
  4. \n
  5. 更深入的观察是,应wait尽可能避免调用 。它效率低下并且占用了 GCD 工作线程之一(这是相当有限的)。如果您曾经从主队列中执行过此操作,则可能会产生相当严重的影响。

    \n

    简而言之,wait我们应该notify

    \n
    let outerQueue = DispatchQueue.global(qos: .default)\nouterQueue.async {\n    let group = DispatchGroup()\n    group.enter()\n    DispatchQueue.global().asyncAfter(deadline: .now()) {\n        //do sth in default queue\n        group.leave()\n    }\n    group.notify(queue: .main) { [weak self] in   // NB: not `wait`\n        self?.doSomething()\n    }\n}\n
    Run Code Online (Sandbox Code Playgroud)\n
  6. \n
\n
\n

你继续问:

\n
\n

苹果文档错了?

\n
class func global(qos: DispatchQoS.QoSClass = .default) -> DispatchQueue\n
Run Code Online (Sandbox Code Playgroud)\n
\n

从技术上讲,混乱的根源不是 的qos参数的默认值global(qos:)。即使明确指定 QoS 为.default,该队列\xe2\x80\x99s 底层qos类仍然是 \xe2\x80\x9cunspecified\xe2\x80\x9d,如上所示。

\n

你说:

\n
\n

我认为这应该是\xe2\x80\x9cdefault\xe2\x80\x9d。

\n
\n

我同意这种观点,但不是在 \xe2\x80\x9cuser-initerated\xe2\x80\x9d 和 \xe2\x80\x9cutility\xe2\x80\x9d 之间使用具有固定 QoS 的默认全局队列,而是让默认全局队列具有 \xe2\x80\x9cunspecified\xe2\x80\x9d QoS 可能是一个有意识的决定,即让它利用适合调用上下文的 QoS 的工作线程。例如,考虑:

\n
DispatchQueue(label: "Background", qos: .background).async {\n    DispatchQueue.global().async {\n        // `Thread.current.qualityOfService` is background; that makes sense\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这种默认/未指定的全局队列行为可能是一个有意识的决定,以避免标准用例中此全局队列上不必要的优先级反转。这可能是设计好的\xe2\x80\x9cfeature\xe2\x80\x9d,而不是\xe2\x80\x9cbug\xe2\x80\x9d。(注意,这似乎仅限于默认的 QoS 全局队列;我们自己的默认 QoS 自定义队列不会体现此行为。)通过 libDispatch源代码,很难判断此行为是否是有意为之(因为没有我看到的代码明确强制执行了这种模式)或者它是否只是一个副作用(有意或无意)。

\n

但我同意你的观点。如果是这样,那就更正确了:

\n
    \n
  1. 全局队列的默认参数是.unspecified(假设没有指定;哈哈);和
  2. \n
  3. 如果您指定显式 QoS .default,则实际上应该是.default,而不是.unspecified
  4. \n
\n

我已经为此开了一张票(尽管我不确定它会被接受,因为纠正这种行为并不完全向后兼容......至少他们可以在文档中澄清)。

\n
\n

旧版《iOS 应用程序能源效率指南》文档描述了默认和未指定的 QoS,如下所示:

\n
\n

默认

\n

该 QoS 的优先级介于用户启动和实用之间。此 QoS 无意供开发人员用来对工作进行分类。没有分配 QoS 信息的工作被视为默认工作,GCD 全局队列在此级别运行。

\n

未指定

\n

这表示缺少 QoS 信息,并提示系统应推断环境 QoS。如果线程使用可能选择线程退出 QoS 的旧版 API,则线程可能具有未指定的 QoS。

\n
\n