概念 編程
爲了在單一進程中充分發揮多核的優點,咱們有必要使用多線程技術(咱們不必去提多進程,這玩意兒和GCD不要緊)。在低層,GCD全局dispatch queue僅僅是工做線程池的抽象。這些隊列中的Block一旦可用,就會被dispatch到工做線程中。提交至用戶隊列的Block最終也會經過全局隊列進入相同的工做線程池(除非你的用戶隊列的目標是主線程,可是爲了提升運行速度,咱們毫不會這麼幹)。 數組
有兩種途徑來經過GCD「榨取」多核心繫統的性能:將單一任務或者一組相關任務併發至全局隊列中運算;將多個不相關的任務或者關聯不緊密的任務併發至用戶隊列中運算; 安全
全局隊列 多線程
設想下面的循環: 併發
for(id obj in array) [self doSomethingIntensiveWith:obj];
假定 -doSomethingIntensiveWith: 是線程安全的且能夠同時執行多個.一個array一般包含多個元素,這樣的話,咱們能夠很簡單地使用GCD來平行運算: app
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for(id obj in array) dispatch_async(queue, ^{ [self doSomethingIntensiveWith:obj]; });
如此簡單,咱們已經在多核心上運行這段代碼了。 異步
固然這段代碼並不完美。有時候咱們有一段代碼要像這樣操做一個數組,可是在操做完成後,咱們還須要對操做結果進行其餘操做: socket
for(id obj in array) [self doSomethingIntensiveWith:obj]; [self doSomethingWith:array];
這時候使用GCD的 dispatch_async 就悲劇了.咱們還不能簡單地使用dispatch_sync來解決這個問題, 由於這將致使每一個迭代器阻塞,就徹底破壞了平行計算。 async
解決這個問題的一種方法是使用dispatch group。一個dispatch group能夠用來將多個block組成一組以監測這些Block所有完成或者等待所有完成時發出的消息。使用函數dispatch_group_create來建立,而後使用函數dispatch_group_async來將block提交至一個dispatch queue,同時將它們添加至一個組。因此咱們如今能夠從新編碼: 函數
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); for(id obj in array) dispatch_group_async(group, queue, ^{ [self doSomethingIntensiveWith:obj]; }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_release(group); [self doSomethingWith:array];
若是這些工做能夠異步執行,那麼咱們能夠更風騷一點,將函數-doSomethingWith:放在後臺執行。咱們使用dispatch_group_async函數創建一個block在組完成後執行:
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); for(id obj in array) dispatch_group_async(group, queue, ^{ [self doSomethingIntensiveWith:obj]; }); dispatch_group_notify(group, queue, ^{ [self doSomethingWith:array]; }); dispatch_release(group);
不只全部數組元素都會被平行操做,後續的操做也會異步執行,而且這些異步運算都會將程序的其餘部分的負載考慮在內。注意若是-doSomethingWith:須要在主線程中執行,好比操做GUI,那麼咱們只要將main queue而非全局隊列傳給dispatch_group_notify函數就好了。
對於同步執行,GCD提供了一個簡化方法叫作dispatch_apply。這個函數調用單一block屢次,並平行運算,而後等待全部運算結束,就像咱們想要的那樣:
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply([array count], queue, ^(size_t index){ [self doSomethingIntensiveWith:[array objectAtIndex:index]]; }); [self doSomethingWith:array];
這很棒,可是異步咋辦?dispatch_apply函數但是沒有異步版本的。可是咱們使用的但是一個爲異步而生的API啊!因此咱們只要用dispatch_async函數將全部代碼推到後臺就好了:
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ dispatch_apply([array count], queue, ^(size_t index){ [self doSomethingIntensiveWith:[array objectAtIndex:index]]; }); [self doSomethingWith:array]; });
簡單的要死!
這種方法的關鍵在於肯定咱們的代碼是在一次對不一樣的數據片斷進行類似的操做。若是你肯定你的任務是線程安全的(不在本篇討論範圍內)那麼你可使用GCD來重寫你的循環了,更平行更風騷。
要看到性能提高,你還得進行一大堆工做。比之線程,GCD是輕量和低負載的,可是將block提交至queue仍是很消耗資源的——block須要被拷貝和入隊,同時適當的工做線程須要被通知。不要將一張圖片的每一個像素做爲一個block提交至隊列,GCD的優勢就半途夭折了。若是你不肯定,那麼請進行試驗。將程序平行計算化是一種優化措施,在修改代碼以前你必須再三思索,肯定修改是有益的(還有確保你修改了正確的地方)。
Subsystem併發運算
前面的章節咱們討論了在程序的單個subsystem中發揮多核心的優點。下來咱們要跨越多個子系統。
例如,設想一個程序要打開一個包含meta信息的文檔。文檔數據自己須要解析並轉換至模型對象來顯示,meta信息也須要解析和轉換。可是,文檔數據和meta信息不須要交互。咱們能夠爲文檔和meta各建立一個dispatch queue,而後併發執行。文檔和meta的解析代碼都會各自串行執行,從而不用考慮線程安全(只要沒有文檔和meta之間共享的數據),可是它們仍是併發執行的。
一旦文檔打開了,程序須要響應用戶操做。例如,可能須要進行拼寫檢查、代碼高亮、字數統計、自動保存或者其餘什麼。若是每一個任務都被實現爲在不一樣的dispatch queue中執行,那麼這些任務會併發執行,並各自將其餘任務的運算考慮在內(respect to each other),從而省去了多線程編程的麻煩。
使用dispatch source(下次我會講到),咱們可讓GCD將事件直接傳遞給用戶隊列。例如,程序中監視socket鏈接的代碼能夠被置於它本身的dispatch queue中,這樣它會異步執行,而且執行時會將程序其餘部分的運算考慮在內。另外,若是使用用戶隊列的話,這個模塊會串行執行,簡化程序。
結論
咱們討論瞭如何使用GCD來提高程序性能以及發揮多核系統的優點。儘管咱們須要比較謹慎地編寫併發程序,GCD仍是使得咱們能更簡單地發揮系統的可用計算資源。
下一篇中,咱們將討論dispatch source,也就是GCD的監視內部、外部事件的機制。