什麼是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⾃自動根據系統負載來增減線程數量,這就減小了上下⽂文切換以及增長了計算效率。 async
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」來獲取這些功能的更多信息。 spa
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函數傳⼊入優先級來訪問隊列。優先級:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
⽤戶隊列: 用戶隊列 (GCD並不這樣稱呼這種隊列, 可是沒有⼀一個特定的名字來形容這種隊列,因此咱們稱其爲⽤用戶隊列) 是⽤用函數 dispatch_queue_create 建立的 隊列. 這些隊列是串⾏行的。正由於如此,它們能夠⽤用來完成同步機制, 有點像傳統線程中的mutex。
建立隊列
要使用戶隊列,咱們⾸首先得建立⼀一個。調⽤用函數dispatch_queue_create就⾏行了。函數的第⼀一個參數是⼀一個標籤,這純是爲了debug。Apple建議咱們使⽤用倒置域名來 命名隊列,⽐好比「com.dreamingwish.subsystem.task」。這些名字會在崩潰⽇日誌中被顯⽰示出來,也能夠被調試器調⽤用,這在調試中會頗有⽤用。第⼆個參數⺫⽬目前還不⽀支持,傳⼊入 NULL就⾏行了。
提交 Job 向⼀一個隊列提交Job很簡單:調⽤用dispatch_async函數,傳⼊入⼀一個隊列和⼀一個block。隊列會在輪到這個block執⾏行時執⾏行這個block的代碼。下⾯面的例⼦子是⼀一個在後臺執
⾏行⼀一個巨⻓長的任務:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self goDoSomethingLongAndInvolved]; NSLog(@"Done doing something long and involved");
});
碼。你能夠簡單地完成這個任務——使⽤用嵌套的dispatch,在外層中執⾏行後臺任務,在內層中將任務dispatch到main queue:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self goDoSomethingLongAndInvolved]; dispatch_async(dispatch_get_main_queue(), ^{
[textField setStringValue:@"Done doing something long and involved"]; });
});
還有一個函數叫dispatch_sync,它乾的事⼉兒和dispatch_async相同,可是它會等待block中的代碼執⾏行完成並返回。結合 __block類型修飾符,能夠⽤用來從執⾏中的 block獲取⼀一個值。例如,你可能有⼀一段代碼在後臺執⾏行,⽽而它須要從界⾯面控制層獲取⼀一個值。那麼你可使⽤用dispatch_sync簡單辦到:
__block NSString *stringValue; dispatch_sync(dispatch_get_main_queue(), ^{
// __block variables aren't automatically retained // so we'd better make sure we have a reference we can keep stringValue = [[textField stringValue] copy];
}); [stringValue autorelease]; // use stringValue in the background now
咱們還可使⽤用更好的⽅方法來完成這件事——使⽤用更「異步」的⻛風格。不一樣於取界⾯面層的值時要阻塞後臺線程,你可使⽤用嵌套的block來中⽌止後臺線程,而後從主線程中 獲取值,而後再將後期處理提交⾄至後臺線程:
dispatch_queue_t bgQueue = myQueue; dispatch_async(dispatch_get_main_queue(), ^{
NSString *stringValue = [[[textField stringValue] copy] autorelease]; dispatch_async(bgQueue, ^{
// use stringValue in the background now });
});
取決於你的需求,myQueue能夠是⽤用戶隊列也可使全局隊列。
再也不使⽤用鎖(Lock) ⽤用戶隊列能夠⽤用於替代鎖來完成同步機制。在傳統多線程編程中,你可能有⼀一個對象要被多個線程使⽤用,你須要⼀一個鎖來保護這個對象:
NSLock *lock;
訪問代碼會像這樣:
- (id)something {
id localSomething; [lock lock]; localSomething = [[something retain] autorelease]; [lock unlock]; return localSomething;
}
- (void)setSomething:(id)newSomething {
[lock lock]; if(newSomething != something) {
[something release]; something = [newSomething retain]; [self updateSomethingCaches];
}
[lock unlock]; }
使⽤用GCD,可使⽤用queue來替代: dispatch_queue_t queue;
要⽤用於同步機制,queue必須是⼀一個⽤用戶隊列,⽽而⾮非全局隊列,因此使⽤用usingdispatch_queue_create初始化⼀一個。而後能夠⽤用dispatch_async 或
dispatch_async 函數會⽴當即返回, block會在後臺異步執⾏行。 固然,一般,任務完成時簡單地NSLog個消息不是個事⼉兒。在典型的Cocoa程序中,你頗有可能但願在任務完成時更新界⾯面,這就意味着須要在主線程中執⾏行⼀一些代
者 dispatch_sync將共享數據的訪問代碼封裝起來:
- (id)something {
__block id localSomething; dispatch_sync(queue, ^{
localSomething = [something retain]; });
return [localSomething autorelease]; }
- (void)setSomething:(id)newSomething {
dispatch_async(queue, ^{ if(newSomething != something) {
} });
}
[something release]; something = [newSomething retain]; [self updateSomethingCaches];
值得注意的是dispatch queue是⾮很是輕量級的,因此你能夠⼤大⽤用特⽤用,就像你之前使⽤用lock⼀同樣。 如今你可能要問:「這樣很好,可是有意思嗎?我就是換了點代碼辦到了同⼀一件事⼉兒。」 實際上,使⽤用GCD途徑有⼏幾個好處:
1. 平行計算: 注意在第⼆二個版本的代碼中, -setSomething:是怎麼使⽤用dispatch_async的。調⽤用 -setSomething:會⽴當即返回,而後這⼀一⼤大堆⼯工做會在後臺執 ⾏行。若是updateSomethingCaches是⼀一個很費時費⼒力的任務,且調⽤用者將要進⾏行⼀一項處理器⾼高負荷任務,那麼這樣作會很棒。
2. 安全: 使⽤用GCD,咱們就不可能意外寫出具備不成對Lock的代碼。在常規Lock代碼中,咱們極可能在解鎖以前讓代碼返回了。使⽤用GCD,隊列一般持續運⾏行,你必將 歸還控制權。
3. 控制: 使⽤用GCD咱們能夠掛起和恢復dispatch queue,⽽而這是基於鎖的⽅方法所不能實現的。咱們還能夠將⼀一個⽤用戶隊列指向另⼀一個dspatch queue,使得這個⽤用戶隊列 繼承那個dispatch queue的屬性。使⽤用這種⽅方法,隊列的優先級能夠被調整——經過將該隊列指向⼀一個不一樣的全局隊列,如有必要的話,這個隊列甚⾄至能夠被⽤用來在 主線程上執⾏行代碼。
4. 集成: GCD的事件系統與dispatch queue相集成。對象須要使⽤用的任何事件或者計時器均可以從該對象的隊列中指向,使得這些句柄能夠⾃自動在該隊列上執⾏行,從⽽而使 得句柄能夠與對象⾃自動同步。
總結
如今你已經知道了GCD的基本概念、怎樣建立dispatch queue、怎樣提交Job⾄至dispatch queue以及怎樣將隊列⽤用做線程同步。接下來我會向你展⽰示如何使⽤用GCD來編 寫平⾏行執⾏行代碼來充分利⽤用多核系統的性能^ ^。我還會討論GCD更深層的東⻄西,包括事件系統和queue targeting。