【问题标题】:When are GCD queues used and when do you know you need them? Swift什么时候使用 GCD 队列,什么时候知道你需要它们?迅速
【发布时间】:2018-05-15 02:40:08
【问题描述】:

在阅读了并发和串行队列、同步和异步之后,我想我对如何创建队列以及它们的执行顺序有了一个想法。我的问题是,在我看过的任何教程中,都没有其实告诉你很多用例。例如:

我有一个网络管理器,它使用 URLSessions 并序列化 json 以向我的 api 发送请求。将它包装在 .utility 队列或 .userInitiated 中是否有意义,或者我只是不将它包装在队列中。

let task = LoginTask(username: username, password: password)

let networkQueue = DispatchQueue(label: "com.messenger.network", 
qos: DispatchQoS.userInitiated)

networkQueue.async {
    task.dataTask(in: dispatcher) { (user, httpCode, error) in
        self.presenter?.loginUserResponse(user: user, httpCode: httpCode, error: error)
    }
}

我的问题是:我是否可以遵循任何指导方针来了解何时需要使用队列,因为我无法在任何地方找到此信息。我意识到苹果提供了示例用法,但是它非常模糊

【问题讨论】:

    标签: swift grand-central-dispatch


    【解决方案1】:

    调度队列的用例很多,很难一一列举,但有两个非常常见的用例如下:

    1. 您希望在当前线程以外的某个线程上运行一些昂贵和/或耗时的进程。当您在主线程上并且想要在后台线程上运行某些东西时,通常会使用此选项。

      图像处理就是一个很好的例子,这是一个众所周知的计算(和内存)密集型过程。因此,您将创建一个用于图像处理的队列,然后将每个图像处理任务分派到该队列。您还可以在 UI 更新完成后将其分派回主队列(因为所有 UI 更新都必须发生在主线程上)。一个常见的模式是:

      imageQueue.async {
          // manipulate the image here
      
          // when done, update the UI:
      
          DispatchQueue.main.async {
              // update the UI and/or model objects on the main thread
          }
      }
      
    2. 您有一些共享资源(它可能是一个简单的变量,也可能是与文件或数据库等其他共享资源的一些交互),无论从哪个线程调用它,您都希望同步这些资源。这通常是更广泛的策略的一部分,该策略使本质上不是线程安全的东西以线程安全的方式运行。

    调度队列的优点是它极大地简化了多线程代码的编写,否则这是一项非常复杂的技术。

    问题是,您的示例发起网络请求时,已经在后台线程上运行了请求,URLSession 为您管理所有这些,因此为此使用队列几乎没有价值。


    为了全面披露,在上面讨论的基本调度队列之外,直接(例如调度组或调度源)或间接(例如操作队列)使用 GCD 的各种不同工具令人惊讶:

    • 调度组:有时您会启动一系列异步任务,并且希望在它们全部完成时收到通知。您可以使用调度组(请参阅https://stackoverflow.com/a/28101212/1271826 以获取随机示例)。这使您无需跟踪所有这些任务何时完成。

    • Dispatch “apply”(现在称为concurrentPerform):有时当您运行一些大规模并行任务时,您希望尽可能多地使用线程。所以concurrentPerform 可以让你有效地并行执行for 循环,Apple 已经针对你的特定设备的内核和 CPU 数量对其进行了优化,同时不会在任何时候用太多的并发任务淹没它,耗尽有限的数量工作线程。有关并行运行 for 循环的示例,请参阅 https://stackoverflow.com/a/39949292/1271826

    • 调度来源:

      • 例如,如果您有一些正在执行大量工作的后台任务,并且您想根据进度更新 UI,有时这些 UI 更新可能会比 UI 处理它们的速度更快。因此,您可以使用调度源(DispatchSourceUserDataAdd)将后台进程与 UI 更新分离。示例见前面提到的https://stackoverflow.com/a/39949292/1271826

      • 传统上,Timer 在主运行循环上运行。但有时您想在后台线程上运行它,但使用Timer 执行此操作很复杂。但是您可以使用DispatchSourceTimer(GCD 计时器)在主队列以外的队列上运行计时器。请参阅https://stackoverflow.com/a/38164203/1271826,了解如何创建和使用调度计时器的示例。调度计时器还可用于避免一些强引用循环,这些循环很容易被基于targetTimer 对象引入。

    • 障碍:有时在使用并发队列时,您希望大多数事情并发运行,但其他事情相对于队列中的其他所有事情串行运行。屏障是一种表示“将此任务添加到队列中,但确保它不会与该队列中的其他任何内容同时运行的方式。”

      障碍的一个例子是读写器模式,其中从某个内存资源的读取可以与所有其他读取同时发生,但任何写入不得与队列中的其他任何内容同时发生。请参阅https://stackoverflow.com/a/28784770/1271826https://stackoverflow.com/a/45628393/1271826

    • 调度信号量:有时您需要让两个运行在不同线程上的任务相互通信。您可以为一个线程使用信号量来“等待”来自另一个线程的“信号”。

      信号量的一个常见应用是使固有的异步任务以更同步的方式运行。

      networkQueue.async {
          let semaphore = DispatchSemaphore(0)
          let task = session.dataTask(with: url) { data, _, error in
              // process the response
      
              // when done, signal that we're done
              semaphore.signal()
          }
          task.resume()
          semaphore.wait(timeout: .distantFuture)
      }
      

      这种方法的优点是在异步网络请求完成之前,分派的任务不会完成。因此,如果您需要发出一系列网络请求,但又不让它们同时运行,信号量可以完成。

      信号量应该谨慎使用,因为它们本质上是低效的(通常阻塞一个线程等待另一个线程)。另外,请确保您永远不要 wait 来自主线程的信号量(因为您违背了执行异步任务的目的)。这就是为什么在上面的示例中,我正在等待networkQueue,而不是主队列。综上所述,通常有比信号量更好的技术,但它有时很有用。

    • 操作队列:操作队列建立在 GCD 调度队列之上,但提供了一些有趣的优势,包括:

      • 能够将固有的异步任务包装在自定义 Operation 子类中。 (这避免了我之前讨论的信号量技术的缺点。)调度队列通常在后台线程上运行固有的同步任务时使用,但有时您想要管理一堆本身就是异步的任务。一个常见的例子是在Operation 子类中包装异步网络请求。

      • 轻松控制并发程度的能力。调度队列可以是串行的,也可以是并发的,但是将控制机制设计为例如说“将排队的任务彼此并发运行,但在任何给定时间不超过四个”是很麻烦的。使用maxConcurrentOperationCount,操作队列使这更容易。 (例如,请参阅https://stackoverflow.com/a/27022598/1271826。)

      • 在各种任务之间建立依赖关系的能力(例如,您可能有一个用于下载图像的队列和另一个用于操作图像的队列)。使用操作队列,您可以有一个用于下载图像的操作和另一个用于处理图像的操作,并且您可以使后者依赖于前者的完成。


    还有很多其他与 GCD 相关的应用程序和技术,但这些都是我经常使用的一些。

    【讨论】:

    • 您好,感谢您的精彩回答,它确实帮助我发现了调度队列的优势。我目前正在努力实现应该是进程间和线程间的信号量(这意味着几个进程 - 每个进程都有几个可能使用这个信号量的线程)。到目前为止,我发现的最好的开箱即用解决方案是使用sem_open API。你认为可以使用调度队列(或者可能是 XPC)来实现这个目标吗? (信号量我需要的 API 当然是等待和信号)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-29
    • 2011-06-09
    • 1970-01-01
    • 2018-01-21
    • 2012-09-16
    • 1970-01-01
    相关资源
    最近更新 更多