2013-10-04 53 views
70

我很努力地完全理解GCD中的併發和串行隊列。我有一些問題,希望有人能夠清楚地回答我的問題。GCD中的併發與串行隊列

  1. 我在讀取串行隊列的創建和使用,以便一個接一個地執行任務。

    • 我創建了一個串行隊列
    • 我用dispatch_async(在我剛剛創建的串行隊列)三次派遣三個塊A,B,C

    請問:但是,如果發生了什麼三個塊來執行:

    • 以便甲

      ,B,C,因爲隊列串行

      OR

    • 同時(在同一時間parralel線程),因爲我用異步分派
  2. 我讀,我可以爲了對方後執行塊一個併發隊列使用dispatch_sync 。在那種情況下,爲什麼串行隊列甚至存在,因爲我總是可以使用一個併發隊列,在那裏我可以按照我想要的那樣同步分配儘可能多的塊?

    感謝您的任何好解釋!

+0

一個簡單的好前提的問題[調度同步VS異步(http://stackoverflow.com/questions/9200558/grand-central-dispatch-async-vs-sync) – Honey

回答

120

一個簡單的例子:你有一個塊需要一分鐘才能執行。您將其添加到主線程的隊列中。我們來看看這四種情況。

  • async - concurrent:代碼在後臺線程上運行。控制立即返回到主線程(和UI)。塊不能認爲它是在該隊列上運行的唯一塊
  • async - serial:代碼在後臺線程上運行。控制立即返回到主線程。塊可以假設它是在該隊列上運行的唯一塊
  • sync - 併發:代碼在後臺線程上運行,但主線程等待它完成,阻止任何UI更新。該塊不能認爲它是在該隊列上運行的唯一塊(我可以在幾秒前使用異步添加另一個塊)
  • sync - serial:代碼在後臺線程上運行,但主線程等待它完成,阻止對UI的任何更新。塊可以假設它是在該隊列

運行顯然,你不會使用任何過去兩年的長時間運行的進程的唯一塊。當您嘗試更新UI(始終在主線程上)時,通常會看到它來自可能在另一個線程上運行的內容。

+3

所以,你告訴我,: (1 )隊列的類型(conc或serial)是決定任務是按順序還是並行執行的唯一元素;; (2)調度類型(同步或異步)只是說執行是否執行或不執行下一條指令?我的意思是,如果我派遣任務SYNC,代碼將會阻塞,直到任務完成,無論它在哪個隊列上執行? –

+7

@BogdanAlexandru正確。隊列決定了執行策略,而不是如何對塊進行排隊。同步等待塊完成,異步不會。 – Jano

+2

謝謝斯蒂芬和@Jano,現在一切都合情合理! –

67

這裏有幾個實驗,我已經做了讓我瞭解這些serialconcurrent隊列與Grand Central Dispatch

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異步運行。異步意味着執行下一行不要等到塊執行後,結果非阻塞主線程&主隊列。 由於它的串行隊列,所有都按照它們添加到串行隊列的順序執行。串行執行的任務總是由與隊列關聯的單個線程一次執行一個。

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使用同步。 Sync會在給定隊列上運行一個塊,並等待它完成,從而導致阻塞主線程或主隊列。由於主隊列需要等到分派塊完成,因此主線程將可用於處理除隊列之外的隊列主隊列。因此有可能在後臺隊列上執行代碼實際上可能在主線程上執行 由於其串行隊列,所有這些隊列都按照它們添加的順序(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使用異步運行。異步意味着執行下一行不要等到塊執行,這會導致非阻塞主線程。 在併發隊列中記住,任務按照它們被添加到隊列中的順序進行處理,但隊列中附加了不同的線程。請記住,它們不應該按照 的順序完成任務,它們將被添加到隊列中。任務的順序每次都不相同 必須自動創建線程。任務並行執行。如果超過 (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使用同步。 Sync會在給定隊列上運行一個塊,並等待它完成,從而導致阻塞主線程或主隊列。由於主隊列需要等到分派塊完成,因此主線程將可用於處理除隊列之外的隊列主隊列。因此有可能在後臺隊列上執行的代碼實際上可能正在主線程上執行。 由於其併發隊列,任務可能無法按照它們添加到隊列的順序完成。但是在同步操作中,儘管它們可能由不同的線程處理。所以,它表現爲這是串行隊列。

下面是這個實驗

的總結記住使用GCD你只添加任務隊列,並從隊列中執行任務。隊列根據操作是同步還是異步來分派主任務或後臺線程中的任務。隊列類型爲串行,併發,主調度隊列。所有執行的任務都默認從主調度隊列完成。已經有4個預定義的全局併發隊列供您的應用程序使用,並且有一個主隊列(DispatchQueue.main)。您也可以手動創建自己的隊列並從該隊列執行任務。

UI通過分派任務主要queue.Short手工具的任務應始終從主線程執行相關的DispatchQueue.main.sync/async而與網絡相關的/重操作應該總是做異步沒有哪個曾經線程使用的是主或事項背景

編輯: 然而,有需要執行網絡調用後臺線程同步操作,而沒有凍結UI的情況下(egrefreshing OAuth令牌,然後等待,如果它成功與否)。你需要來包裝方法在異步操作中。這樣,您的繁重操作就按順序執行,並且不會阻塞mai n線程。

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") 
     } 
    } 
} 

編輯編輯:您可以觀看演示視頻here

+0

偉大的演示.... *下一行不要等到塊執行結果非阻塞主線程*這就是爲什麼如果你在後臺線程使用斷點,它會跳轉到'}',因爲它真的沒有執行在那一刻 – Honey

+0

@那懶惰的iOS Guy웃我還是不明白異步併發和異步串行的區別。什麼是使用兩者的含義。他們都在後臺運行,不會打擾用戶界面。爲什麼你會使用同步?不是所有的代碼同步。一個接一個地? – eonist

+1

@GitSyncApp你可以觀看視頻[這裏](https://www.youtube.com/watch?v=YhZahnTiA8U) –

2

如果我正確理解有關GCD是如何工作的,我覺得有兩種類型的DispatchQueueserialconcurrent,在同一時間,有兩種方式DispatchQueue調度其任務,分配closure,第一個是async,另一個是sync。這些共同決定了封閉(任務)如何實際執行。

我發現serialconcurrent意思是有多少是隊列可以使用線程,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() 

希望它可以幫助。