【问题标题】:Does DispatchQueue run in its own thread?DispatchQueue 是否在自己的线程中运行?
【发布时间】:2021-05-10 21:23:22
【问题描述】:

我试图了解DispatchQueues 的真正工作原理,我想知道假设DispatchQueues 有自己的管理线程是否安全?例如,让我们以串行队列为例。在我的理解中——因为它是串行的,新任务只能在当前任务结束后启动,所以“某人”必须从队列中取出新任务并提交执行!因此,基本上,队列似乎必须留在自己的线程中,该线程将调度存储在队列中的任务。这是正确的还是我误解了什么?

【问题讨论】:

  • Gcd 被嵌入内核。因此,它在您可以认为是线程的任何事物之外进行控制。它在基本的底层世界中受到控制,线程在其中生活、移动和存在。
  • @matt 所以,如果我正确理解了你的观点,那么下一个任务将由内核直接出列并调度。简而言之,通过某种本质(基本上是 Grand Central Dispatch),它是内核的一部分,它管理所有队列并能够在当前任务完成后直接将下一个任务出列并提交执行?我猜对了吗?
  • 我是这么理解的。这就是 GCD 如此高效的原因。你正在寻找一种“上帝”,他的工作是让 GCD 队列/线程做他们所做的事情。那是内核;它负责线程,所以它本身不能说是in线程。它只是“是”,一直都是。
  • 可以看一下源码:github.com/apple/swift-corelibs-libdispatch。 Swift GCD 包装了调用内核函数的 c 级 libdispatch。您应该在脑海中将线程与队列分开,队列是指工作的顺序,GCD 会在线程上适当地执行工作。如果需要更多线程,则系统将创建更多线程。源代码中有很多 cmets 解释了其中的一些。您可以让所有串行/并发队列使用同一个线程,但 GCD 会在可用时管理创建更多线程并根据需要提高效率。
  • 这种“管理线程”的概念实际上没有意义(也没有关系)。应用程序开发人员的突出问题是 GCD 有工作线程池,当我们将代码分派到队列时,GCD 将控制 (a) 何时以及 (b) 代码将在哪个工作线程上运行。 (这就是 GCD 如何通过幼稚的 Thread 实现获得性能提升:它最大限度地减少了新线程旋转的高成本开销,并简单地利用了现有的工作线程池。)你真的在问 GCD 内部是如何工作的吗?或者您只是想知道您分派的代码在哪个线程上运行?

标签: swift grand-central-dispatch dispatch-queue


【解决方案1】:

macOS 上的 GCD 确实包含一些来自内核的直接支持。所有代码都是开源的,可以查看:https://opensource.apple.com/source/libdispatch/

但是,作为 Swift 开源项目 (swift-corelibs-libdispatch) 的一部分,可以使用相同 Dispatch API 的 Linux 实现。此实现不使用任何特殊的内核支持,而只是使用 pthreads 实现。从该项目的自述文件中:

Darwin [macOS] 上的 libdispatch 是 xnu 内核中的逻辑与用户空间库的组合。内核拥有最多的信息来平衡整个系统的工作负载。然而,作为第一步,我们认为在 Linux 上使用用户空间 pthread 原语来提供库的基本功能是有用的。最终,可以开发一个 Linux 内核模块来支持更明智的线程调度。

为了具体解决您的问题 - 每个队列都有一个管理线程是不正确的。队列更像是一种有助于管理线程的数据结构——一种抽象,使您作为开发人员不必考虑线程细节。

如何创建和使用线程取决于系统,并且可以根据您对队列的操作而有所不同。例如,在队列上使用.sync() 通常只是获取锁并在调用线程上执行块,即使队列是并发队列也是如此。您可以通过设置断点并观察您正在运行的线程来看到这一点:

let syncQueue = DispatchQueue(label: "syncQueue", attributes: .concurrent)

print("Before sync")
syncQueue.sync {
  print("On queue")
}
print("After sync")

另一方面,多个异步任务可以同时在一个并发队列上运行,由多个线程支持。在实践中,全局队列似乎一次最多使用 64 个线程(代码打印“已使用 64 个线程”):

var threads: Set<Thread> = []
let threadQueue = DispatchQueue(label: "threads set")

let group = DispatchGroup()

for _ in 0..<100 {
  group.enter()
  DispatchQueue.global(qos: .default).async {
    sleep(2)
    let thisThread = Thread.current
    threadQueue.sync { _ = threads.insert(thisThread) }
    group.leave()
  }
}

group.wait()  // wait for all async() blocks to finish

print("Used \(threads.count) threads")

但是如果没有sleep(),任务会很快完成,并且系统不需要使用这么多线程(程序会打印“Used 20 threads”,或者30,或者更低的数字)。

main 队列是另一个串行队列,它作为应用程序生命周期的一部分运行,或者可以使用 dispatchMain() 手动运行。

【讨论】:

    【解决方案2】:

    不,您不应该假设 DispatchQueue 有自己的托管线程,并且它不必在同一个线程上执行所有任务。它只保证在上一个任务完成之后再接下一个任务:

    提交到调度队列的工作在系统管理的线程池上执行。除了代表应用主线程的调度队列外,系统不保证它使用哪个线程来执行任务。

    (source)

    实际上,非常有可能,同一个线程将运行来自同一个顺序队列的多个或所有顺序任务 - 只要它们彼此靠近(及时)运行。我会推测这不是纯粹的巧合,而是通过优化(避免上下文切换)。但它不是保证

    其实你可以做这个小实验:

    let serialQueue = DispatchQueue(label: "my.serialqueue")
    var incr: Int = 0
    
    DispatchQueue.concurrentPerform(iterations: 5) { iteration in
    
        // Rundomize time of access to serial queue
        sleep(UInt32.random(in: 1...30))
        
        // Schedule execution on serial queue
        serialQueue.async {
            incr += 1
            print("\(iteration) \(Date().timeIntervalSince1970) incremented \(incr) on \(Thread.current)")
        }
    }
    

    你会看到这样的:

    3 1612651601.6909518 incremented 1 on <NSThread: 0x600000fa0d40>{number = 7, name = (null)}
    4 1612651611.689259 incremented 2 on <NSThread: 0x600000faf280>{number = 9, name = (null)}
    0 1612651612.68934 incremented 3 on <NSThread: 0x600000fb4bc0>{number = 3, name = (null)}
    2 1612651617.690246 incremented 4 on <NSThread: 0x600000fb4bc0>{number = 3, name = (null)}
    1 1612651622.690335 incremented 5 on <NSThread: 0x600000faf280>{number = 9, name = (null)}
    

    迭代同时开始,但我们让它们随机休眠,以便它们在不同时间访问串行队列。结果是虽然任务执行是完全按顺序执行的,但同一个线程不太可能接手每个任务。

    现在,如果您删除顶部的sleep,导致所有迭代同时请求访问顺序队列,您很可能会看到所有任务将在同一个线程上运行,我认为这是优化,而不是巧合:

    4 1612651665.3658218 incremented 1 on <NSThread: 0x600003c94880>{number = 6, name = (null)}
    3 1612651665.366118 incremented 2 on <NSThread: 0x600003c94880>{number = 6, name = (null)}
    2 1612651665.366222 incremented 3 on <NSThread: 0x600003c94880>{number = 6, name = (null)}
    0 1612651665.384039 incremented 4 on <NSThread: 0x600003c94880>{number = 6, name = (null)}
    1 1612651665.3841062 incremented 5 on <NSThread: 0x600003c94880>{number = 6, name = (null)}
    

    这是关于iOS Concurrency "Underlying Truth"主题的精彩读物

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多