GCD中的并发与串行队列

我正在努力完全理解GCD中的并发和串行队列。 我有一些问题,希望有人能够清楚地回答我的问题。

  1. 我正在读取串行队列被创build和使用,以便一个接一个地执行任务。 但是,如果发生以下情况:

    • 我创build一个串行队列
    • 我使用dispatch_async (在我刚创build的串行队列上)三次分派三个块A,B,C

    这三个块是否会被执行:

    • 按顺序A,B,C,因为队列是串行的

      要么

    • 同时(在parralel线程同时),因为我使用ASYNC调度
  2. 我读到我可以在并发队列上使用dispatch_sync来一个接一个地执行块。 在这种情况下,为什么串行队列甚至存在,因为我总是可以使用一个并发队列,在那里我可以同步调度尽可能多的块?

    感谢您的任何好解释!

一个简单的例子:你有一个块需要一分钟才能执行。 您将其添加到主线程的队列中。 我们来看看这四种情况。

  • asynchronous – 并发:代码在后台线程上运行。 控制立即返回到主线程(和UI)。 该块不能认为它是在该队列上运行的唯一块
  • async – serial:代码在后台线程上运行。 控制立即返回到主线程。 该块可以假设它是在该队列上运行的唯一块
  • 同步 – 并发:代码在后台线程上运行,但主线程等待它完成,阻止任何更新到用户界面。 该块不能认为它是该队列上运行的唯一块(我可以在几秒前使用asynchronous添加另一个块)
  • 同步 – 串行:代码在后台线程上运行,但主线程等待它完成,阻止任何更新到用户界面。 该块可以假设它是在该队列上运行的唯一块

显然你不会使用最后两个长时间运行的进程。 当你试图更新UI(总是在主线程上)的时候,通常会看到它可能在另一个线程上运行的东西。

这里有几个实验,我已经做了让我了解这些serial ,与Grand Central Dispatch concurrent队列。

  func doLongAsyncTaskInSerialQueue() { let serialQueue = DispatchQueue(label: "com.queue.Serial") for i in 1...5 { serialQueue.async { if Thread.isMainThread{ print("task running in main thread") }else{ print("task running in background thread") } let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")! let _ = try! Data(contentsOf: imgURL) print("\(i) completed downloading") } } } 

在GCD中使用asynchronous时,任务将在不同的线程(主线程除外)中运行。 asynchronous意味着执行下一行不要等到块执行结果非阻塞主线程和主队列。 由于其串行队列,所有这些都按照它们被添加到串行队列的顺序来执行。串行执行的任务总是由与队列相关联的单个线程一次执行一个。

 func doLongSyncTaskInSerialQueue() { let serialQueue = DispatchQueue(label: "com.queue.Serial") for i in 1...5 { serialQueue.sync { if Thread.isMainThread{ print("task running in main thread") }else{ print("task running in background thread") } let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")! let _ = try! Data(contentsOf: imgURL) print("\(i) completed downloading") } } } 

在GCD中使用同步时,任务可能会在主线程中运行。 同步在给定的队列上运行一个块并等待它完成,从而导致阻塞主线程或主队列。由于主队列需要等待,直到分派的块完成,主线程将可用于处理来自队列以外的队列主队列。因此在后台队列上执行的代码有可能在主线程上实际执行。由于它的串行队列,所有这些队列都按照它们添加的顺序(FIFO)执行。

 func doLongASyncTaskInConcurrentQueue() { let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent) for i in 1...5 { concurrentQueue.async { if Thread.isMainThread{ print("task running in main thread") }else{ print("task running in background thread") } let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")! let _ = try! Data(contentsOf: imgURL) print("\(i) completed downloading") } print("\(i) executing") } } 

在GCD中使用asynchronous时,任务将在后台线程中运行。 asynchronous意味着执行下一行不要等到块执行结果非阻塞主线程。 请记住,在并发队列中,按照添加到队列中的顺序来处理任务,但将不同的线程连接到队列。 请记住,它们不应按照它们添加到队列中的顺序来完成任务。每次创build线程时,任务的顺序都会有所不同。必须自动执行线程任务。并行执行任务。 如果超过了(maxConcurrentOperationCount),那么有些任务会像串行一样工作,直到线程空闲。

 func doLongSyncTaskInConcurrentQueue() { let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent) for i in 1...5 { concurrentQueue.sync { if Thread.isMainThread{ print("task running in main thread") }else{ print("task running in background thread") } let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")! let _ = try! Data(contentsOf: imgURL) print("\(i) completed downloading") } print("\(i) executed") } } 

在GCD中使用同步时,任务可能会在主线程中运行。 同步在给定的队列上运行一个块并等待它完成,从而导致阻塞主线程或主队列。由于主队列需要等待,直到分派的块完成,主线程将可用于处理来自队列以外的队列主队列。因此,在主线程上可能实际上正在执行在后台队列上执行的代码。 由于其并发队列,任务可能无法按照它们添加到队列的顺序完成。 但是在同步操作中,尽pipe它们可能由不同的线程处理。 所以,它performance为这是串行队列。

这里是这个实验的总结

请记住使用GCD,您只是将任务添加到队列并从该队列执行任务。 队列根据操作是同步的还是asynchronous的,在主线程或后台线程中分派任务。 所有执行的任务都是默认从主调度队列完成的。已经有四个预定义的全局并发队列供您的应用程序使用,一个主队列(DispatchQueue.main)。您也可以手动创build自己的队列并从该队列中执行任务。

UI相关的任务应该总是通过调度任务到主队列从主线程执行。短手实用程序是DispatchQueue.main.sync/async而networking相关/繁重的操作应该总是asynchronous完成的,不pipe你使用哪一个主线程或背景

编辑:但是,有些情况下,您需要在后台线程中同步执行networking调用操作,而不冻结UI(例如刷新OAuth令牌,如果成功或不成功等待)。您需要在asynchronous操作内包装该方法。操作按顺序执行,并且不阻塞主线程。

 func doMultipleSyncTaskWithinAsynchronousOperation() { let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent) concurrentQueue.async { let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default) for i in 1...5 { concurrentQueue.sync { let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")! let _ = try! Data(contentsOf: imgURL) print("\(i) completed downloading") } print("\(i) executed") } } } 

编辑编辑:你可以在这里观看演示video

如果我正确理解GCD是如何工作的,我认为有两种types的DispatchQueueserialconcurrent ,同时DispatchQueue调度它的任务有两种方式,分配的closure ,第一个是async ,另一个是sync 。 这些共同决定了闭包(任务)如何实际执行。

我发现serialconcurrent意味着有多less队列可以使用的线程, serial意味着一个,而concurrent意味着许多。 syncasync意味着任务将在哪个线程,调用者的线程或该队列的底层线程上执行, sync意味着在调用者线程上运行,而async意味着在底层线程上运行。

以下是可以在Xcode游乐场上运行的实验代码。

 PlaygroundPage.current.needsIndefiniteExecution = true let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent) let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent) let sq = DispatchQueue(label: "serial.queue") func codeFragment() { print("code Fragment begin") print("Task Thread:\(Thread.current.description)") let imgURL = URL(string: "http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")! let _ = try! Data(contentsOf: imgURL) print("code Fragment completed") } func serialQueueSync() { sq.sync { codeFragment() } } func serialQueueAsync() { sq.async { codeFragment() } } func concurrentQueueSync() { cq2.sync { codeFragment() } } func concurrentQueueAsync() { cq2.async { codeFragment() } } func tasksExecution() { (1...5).forEach { (_) in /// Using an concurrent queue to simulate concurent task executions. cq.async { print("Caller Thread:\(Thread.current.description)") /// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue. //serialQueueAsync() /// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers' threads. //serialQueueSync() /// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads //concurrentQueueAsync() /// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers' thread //concurrentQueueSync() } } } tasksExecution() 

希望能有所帮助。