天天看點

GCD介紹

(一): 基本概念和Dispatch Queue

什麼是GCD?

Grand Central Dispatch或者GCD,是一套低層API,提供了一種新的方法來進行并發程式編寫。從基本功能上講,GCD有點像NSOperationQueue,他們都允許程式将任務切分為多個單一任務然後送出至工作隊列來并發地或者串行地執行。GCD比之NSOpertionQueue更底層更高效,并且它不是Cocoa架構的一部分。

除了代碼的平行執行能力,GCD還提供高度內建的事件控制系統。可以設定句柄來響應檔案描述符、mach ports(Mach port 用于 OS X上的程序間通訊)、程序、計時器、信号、使用者生成事件。這些句柄通過GCD來并發執行。

GCD的API很大程度上基于block,當然,GCD也可以脫離block來使用,比如使用傳統c機制提供函數指針和上下文指針。實踐證明,當配合block使用時,GCD非常簡單易用且能發揮其最大能力。

你可以在Mac上敲指令“man dispatch”來擷取GCD的文檔。

為何使用?

GCD提供很多超越傳統多線程程式設計的優勢:

易用: GCD比之thread跟簡單易用。由于GCD基于work unit而非像thread那樣基于運算,是以GCD可以控制諸如等待任務結束、監視檔案描述符、周期執行代碼以及工作挂起等任務。基于block的血統導緻它能極為簡單得在不同代碼作用域之間傳遞上下文。

效率: GCD被實作得如此輕量和優雅,使得它在很多地方比之專門建立消耗資源的線程更實用且快速。這關系到易用性:導緻GCD易用的原因有一部分在于你可以不用擔心太多的效率問題而僅僅使用它就行了。

性能: GCD自動根據系統負載來增減線程數量,這就減少了上下文切換以及增加了計算效率。

Dispatch Objects

盡管GCD是純c語言的,但它被組建成面向對象的風格。GCD對象被稱為dispatch object。Dispatch object像Cocoa對象一樣是引用計數的。使用dispatch_release和dispatch_retain函數來操作dispatch object的引用計數來進行記憶體管理。但主意不像Cocoa對象,dispatch object并不參與垃圾回收系統,是以即使開啟了GC,你也必須手動管理GCD對象的記憶體。

Dispatch queues 和 dispatch sources(後面會介紹到)可以被挂起和恢複,可以有一個相關聯的任意上下文指針,可以有一個相關聯的任務完成觸發函數。可以查閱“man dispatch_object”來擷取這些功能的更多資訊。

Dispatch Queues

GCD的基本概念就是dispatch queue。dispatch queue是一個對象,它可以接受任務,并将任務以先到先執行的順序來執行。dispatch queue可以是并發的或串行的。并發任務會像NSOperationQueue那樣基于系統負載來合适地并發進行,串行隊列同一時間隻執行單一任務。

GCD中有三種隊列類型:

The main queue: 與主線程功能相同。實際上,送出至main queue的任務會在主線程中執行。main queue可以調用dispatch_get_main_queue()來獲得。因為main queue是與主線程相關的,是以這是一個串行隊列。

Global queues: 全局隊列是并發隊列,并由整個程序共享。程序中存在三個全局隊列:高、中(預設)、低三個優先級隊列。可以調用dispatch_get_global_queue函數傳入優先級來通路隊列。

使用者隊列: 使用者隊列 (GCD并不這樣稱呼這種隊列, 但是沒有一個特定的名字來形容這種隊列,是以我們稱其為使用者隊列) 是用函數 dispatch_queue_create 建立的隊列. 這些隊列是串行的。正因為如此,它們可以用來完成同步機制, 有點像傳統線程中的mutex。

建立隊列

要使用使用者隊列,我們首先得建立一個。調用函數dispatch_queue_create就行了。函數的第一個參數是一個标簽,這純是為了debug。Apple建議我們使用倒置域名來命名隊列,比如“com.dreamingwish.subsystem.task”。這些名字會在崩潰日志中被顯示出來,也可以被調試器調用,這在調試中會很有用。第二個參數目前還不支援,傳入NULL就行了。

dispatch隊列的生成可以有這幾種方式:

1. dispatch_queue_t queue = dispatch_queue_create(“com.dispatch.serial”, DISPATCH_QUEUE_SERIAL); //生成一個串行隊列,隊列中的block按照先進先出(FIFO)的順序去執行,實際上為單線程執行。第一個參數是隊列的名稱,在調試程式時會非常有用,所有盡量不要重名了。

2. dispatch_queue_t queue = dispatch_queue_create(“com.dispatch.concurrent”, DISPATCH_QUEUE_CONCURRENT); //生成一個并發執行隊列,block被分發到多個線程去執行

3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //獲得程式程序預設産生的并發隊列,可設定優先級來選擇高、中、低三個優先級隊列。由于是系統預設生成的,是以無法調用dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。需要注意的是,三個隊列不代表三個線程,可能會有更多的線程。并發隊列可以根據實際情況來自動産生合理的線程數,也可了解為dispatch隊列實作了一個線程池的管理,對于程式邏輯是透明的。

官網文檔解釋說共有三個并發隊列,但實際還有一個更低優先級的隊列,設定優先級為DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode調試時可以觀察到正在使用的各個dispatch隊列。

4. dispatch_queue_t queue = dispatch_get_main_queue(); //獲得主線程的dispatch隊列,實際是一個串行隊列。同樣無法控制主線程dispatch隊列的執行繼續或中斷。

多核心的性能

為了在單一程序中充分發揮多核的優勢,我們有必要使用多線程技術(我們沒必要去提多程序,這玩意兒和GCD沒關系)。在低層,GCD全局dispatch queue僅僅是工作線程池的抽象。這些隊列中的Block一旦可用,就會被dispatch到工作線程中。送出至使用者隊列的Block最終也會通過全局隊列進入相同的工作線程池(除非你的使用者隊列的目标是主線程,但是為了提高運作速度,我們絕不會這麼幹)。

有兩種途徑來通過GCD“榨取”多核心系統的性能:将單一任務或者一組相關任務并發至全局隊列中運算;将多個不相關的任務或者關聯不緊密的任務并發至使用者隊列中運算;

全局隊列

設想下面的循環:

for(id obj in array)

[self doSomethingIntensiveWith:obj];

假定 -doSomethingIntensiveWith: 是線程安全的且可以同時執行多個.一個array通常包含多個元素,這樣的話,我們可以很簡單地使用GCD來平行運算:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

for(id obj in array)

dispatch_async(queue, ^{

[self doSomethingIntensiveWith:obj];

});

如此簡單,我們已經在多核心上運作這段代碼了。

dispatch group

當然這段代碼并不完美。有時候我們有一段代碼要像這樣操作一個數組,但是在操作完成後,我們還需要對操作結果進行其他操作

for(id obj in array)

[self doSomethingIntensiveWith:obj];

[self doSomethingWith:array];

這時候使用GCD的 dispatch_async 就悲劇了.我們還不能簡單地使用dispatch_sync來解決這個問題, 因為這将導緻每個疊代器阻塞,就完全破壞了平行計算。

解決這個問題的一種方法是使用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函數就行了

dispatch_apply

對于同步執行,GCD提供了一個簡化方法叫做dispatch_apply。這個函數調用單一block多次,并平行運算,然後等待所有運算結束,就像我們想要的那樣:

spatch_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];

});

dispatch source

何為Dispatch Sources

簡單來說,dispatch source是一個監視某些類型事件的對象。當這些事件發生時,它自動将一個block放入一個dispatch queue的執行例程中。

原文:http://iliunian.cn./GCD.html

繼續閱讀