概念:node
程序中同步和異步是什麼意思?有什麼區別?編程
解釋一:
串行執行至關於同步數組
併發執行至關於異步緩存
GCD介紹(一): 基本概念和Dispatch Queue安全
GCD提供不少超越傳統多線程編程的優點:服務器
Dispatch Objects多線程
GCD對象被稱爲dispatch object。Dispatch object像Cocoa對象同樣是引用計數的。使用dispatch_release和dispatch_retain函數來操做dispatch object的引用計數來進行內存管理。併發
但注意不像Cocoa對象,dispatch object並不參與垃圾回收系統,因此即便開啓了ARC,你也必須手動管理GCD對象的內存。app
Dispatch queues 和 dispatch sources(後面會介紹到)能夠被掛起和恢復,能夠有一個相關聯的任意上下文指針,能夠有一個相關聯的任務完成觸發函數。異步
Dispatch Queues
GCD的基本概念就是dispatch queue。dispatch queue是一個對象,它能夠接受任務,並將任務以先到先執行的順序來執行。dispatch queue能夠是併發的或串行的。併發任務會像NSOperationQueue那樣基於系統負載來合適地併發進行,串行隊列同一時間只執行單一任務。
GCD中有三種隊列類型:
建立隊列
要使用用戶隊列,咱們首先得建立一個。調用函數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_async 函數會當即返回, block會在後臺異步執行。
固然,一般,任務完成時簡單地NSLog個消息不是個事兒。在典型的Cocoa程序中,你很 有可能但願在任務完成時更新界面,這就意味着須要在主線 程中執行一些代碼。你能夠簡單地完成這個任務——使用嵌套的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_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途徑有幾個好處:
總結
如今你已經知道了GCD的基本概念、怎樣建立dispatch queue、怎樣提交Job至dispatch queue以及怎樣將隊列用做線程同步。接下來我會向你展現如何使用GCD來編寫平行執行代碼來充分利用多核系統的性能^ ^。我還會討論GCD更深層的東西,包括事件系統和queue targeting。
GCD介紹(二): 多核心的性能
概念
爲了在單一進程中充分發揮多核的優點,咱們有必要使用多線程技術(咱們不必去提多進程,這 玩意兒和GCD不要緊)。在低層,GCD全局 dispatch queue僅僅是工做線程池的抽象。這些隊列中的Block一旦可用,就會被dispatch到工做線程中。提交至用戶隊列的Block最終也會經過全局 隊列進入相同的工做線程池(除非你的用戶隊列的目標是主線程,可是爲了提升運行速度,咱們毫不會這麼幹)。
有兩種途徑來經過GCD「榨取」多核心繫統的性能:將單一任務或者一組相關任務併發至全局隊列中運算;將多個不相關的任務或者關聯不緊密的任務併發至用戶隊列中運算;
全局隊列
設想下面的循環:
1 2 |
for(id obj in array) [self doSomethingIntensiveWith:obj]; |
假定 -doSomethingIntensiveWith: 是線程安全的且能夠同時執行多個.一個array一般包含多個元素,這樣的話,咱們能夠很簡單地使用GCD來平行運算:
1 2 3 4 5 |
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); for(id obj in array) dispatch_async(queue, ^{ [self doSomethingIntensiveWith:obj]; }); |
如此簡單,咱們已經在多核心上運行這段代碼了。
固然這段代碼並不完美。有時候咱們有一段代碼要像這樣操做一個數組,可是在操做完成後,咱們還須要對操做結果進行其餘操做:
1 2 3 |
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,同時將它們添加至一個組。因此咱們如今能夠從新代碼:
1 2 3 4 5 6 7 8 9 10 |
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在組完成後執行:
1 2 3 4 5 6 7 8 9 10 |
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屢次,並平行運算,而後等待全部運算結束,就像咱們想要的那樣:
1 2 3 4 5 |
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函數將全部代碼推到後臺就好了:
1 2 3 4 5 6 7 |
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的監視內部、外部事件的機制。
GCD介紹(三): Dispatch Sources
何爲Dispatch Sources
簡單來講,dispatch source是一個監視某些類型事件的對象。當這些事件發生時,它自動將一個block放入一個dispatch queue的執行例程中。
說的貌似有點不清不楚。咱們到底討論哪些事件類型?
下面是GCD 10.6.0版本支持的事件:
這是一堆頗有用的東西,它支持全部kqueue所支持的事件(kqueue是什麼?見 http://en.wikipedia.org/wiki/Kqueue)以及mach(mach是什麼?見http: //en.wikipedia.org/wiki/Mach_(kernel))端口、內建計時器支持(這樣咱們就不用使用超時參數來建立本身的計時器) 和用戶事件。
用戶事件
這些事件裏面多數均可以從名字中看出含義,可是你可能想知道啥叫用戶事件。簡單地說,這種事件是由你調用dispatch_source_merge_data函數來向本身發出的信號。
這個名字對於一個發出事件信號的函數來講,太怪異了。這個名字的來由是GCD會在事件句柄被 執行以前自動將多個事件進行聯結。你能夠將數據「拼接」 至dispatch source中任意次,而且若是dispatch queue在這期間繁忙的話,GCD只會調用該句柄一次(不要以爲這樣會有問題,看完下面的內容你就明白了)。
用戶事件有兩 種: DISPATCH_SOURCE_TYPE_DATA_ADD 和 DISPATCH_SOURCE_TYPE_DATA_OR.用戶事件源有 個 unsigned long data屬性,咱們將一個 unsigned long傳入 dispatch_source_merge_data。當使用 _ADD版本時,事件在聯結時會把這些數字相加。當使用 _OR版本時, 事件在聯結時會把這些數字邏輯與運算。當事件句柄執行時,咱們可使用dispatch_source_get_data函數訪問當前值,而後這個值會被 重置爲0。
讓我假設一種狀況。假設一些異步執行的代碼會更新一個進度條。由於主線程只不過是GCD的另 一個dispatch queue而已,因此咱們能夠將GUI更新工做push到主線程中。然而,這些事件可能會有一大堆,咱們不想對GUI進行頻繁而累贅的更新,理想的狀況是 當主線程繁忙時將全部的改變聯結起來。
用dispatch source就完美了,使用DISPATCH_SOURCE_TYPE_DATA_ADD,咱們能夠將工做拼接起來,而後主線程能夠知道從上一次處理完事件到如今一共發生了多少改變,而後將這一整段改變一次更新至進度條。
啥也不說了,上代碼:
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(source, ^{
[progressIndicator incrementBy:dispatch_source_get_data(source)];
});
dispatch_resume(source);
dispatch_apply([array count], globalQueue, ^(size_t index) {
// do some work on data at index
dispatch_source_merge_data(source, 1);
});
(對於這段代碼,我很想說點什麼,我第一次用dispatch source時,我糾結了好久,非常崩潰:Dispatch queue啓動時默認狀態是掛起的,咱們建立完畢以後得主動恢復,不然事件不會被傳送)
假設你已經將進度條的min/max值設置好了,那麼這段代碼就完美了。數據會被併發處理。 當每一段數據完成後,會通知dispatch source並將dispatch source data加1,這樣咱們就認爲一個單元的工做完成了。事件句柄根據已完成的工做單元來更新進度條。若主線程比較空閒而且這些工做單元進行的比較慢,那麼事 件句柄會在每一個工做單元完成的時候被調用,實時更新。若是主線程忙於其餘工做,或者工做單元完成速度很快,那麼完成事件會被聯結起來,致使進度條只在主線 程變得可用時才被更新,而且一次將積累的改變動新至GUI。
如今你可能會想,聽起來卻是不錯,可是要是我不想讓事件被聯結呢?有時候你可能想讓每一次信 號都會引發響應,什麼後臺的智能玩意兒通通不要。啊。。 其實很簡單的,把你的思想放到禁錮的框子以外就好了。若是你想讓每個信號都獲得響應,那使用dispatch_async函數不就好了。實際上,使用的 dispatch source而不使用dispatch_async的惟一緣由就是利用聯結的優點。
內建事件
上面就是怎樣使用用戶事件,那麼內建事件呢?看看下面這個例子,用GCD讀取標準輸入:
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t stdinSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
STDIN_FILENO,
0,
globalQueue);
dispatch_source_set_event_handler(stdinSource, ^{
char buf[1024];
int len = read(STDIN_FILENO, buf, sizeof(buf));
if(len > 0)
NSLog(@"Got data from stdin: %.*s", len, buf);
});
dispatch_resume(stdinSource);
簡單的要死!由於咱們使用的是全局隊列,句柄自動在後臺執行,與程序的其餘部分並行,這意味着對這種狀況的提速:事件進入程序時,程序正在處理其餘事務。
這是標準的UNIX方式來處理事務的好處,不用去寫loop。若是使用經典的 read調 用,咱們還得萬分留神,由於返回的數據可能比請求的少,還得忍受無厘頭的「errors」,好比 EINTR (終端系統調用)。使用GCD,咱們啥都不 用管,就從這些蛋疼的狀況裏解脫了。若是咱們在文件描述符中留下了未讀取的數據,GCD會再次調用咱們的句柄。
對於標準輸入,這沒什麼問題,可是對於其餘文件描述符,咱們必須考慮在完成讀寫以後怎樣清除 描述符。對於dispatch source還處於活躍狀態時,咱們決不能關閉描述符。若是另外一個文件描述符被建立了(多是另外一個線程建立的)而且新的描述符恰好被分配了相同的數字, 那麼你的dispatch source可能會在不該該的時候忽然進入讀寫狀態。de這個bug可不是什麼好玩的事兒。
適當的清除方式是使 用 dispatch_source_set_cancel_handler,並傳入一個block來關閉文件描述符。而後咱們使 用 dispatch_source_cancel來取消dispatch source,使得句柄被調用,而後文件描述符被關閉。
使用其餘dispatch source類型也差很少。總的來講,你提供一個source(mach port、文件描述符、進程ID等等)的區分符來做爲diapatch source的句柄。mask參數一般不會被使用,可是對於 DISPATCH_SOURCE_TYPE_PROC 來講mask指的是咱們想要接受哪一 種進程事件。而後咱們提供一個句柄,而後恢復這個source(前面我加粗字體所說的,得先恢復),搞定。dispatch source也提供一個特定於source的data,咱們使用 dispatch_source_get_data函數來訪問它。例如,文件描述符會給 出大體可用的字節數。進程source會給出上次調用以後發生的事件的mask。具體每種source給出的data的含義,看man page吧。
計時器
計時器事件稍有不一樣。它們不使用handle/mask參數,計時器事件使用另一個函數 dispatch_source_set_timer 來配置計時器。這個函數使用三個參數來控制計時器觸發:
start參數控制計時器第一次觸發的時刻。參數類型 是 dispatch_time_t,這是一個opaque類型,咱們不能直接操做它。咱們得須要 dispatch_time 和 dispatch_walltime 函數來建立它們。另外,常量 DISPATCH_TIME_NOW 和DISPATCH_TIME_FOREVER 一般頗有用。
interval參數沒什麼好解釋的。
leeway參數比較有意思。這個參數告訴系統咱們須要計時器觸發的精準程度。全部的計時 器都不會保證100%精準, 這個參數用來告訴系統你但願系統保證精準的努力程度。若是你但願一個計時器沒五秒觸發一次,而且越準越好,那麼你傳遞0爲參數。另外,若是是一個週期性任 務,好比檢查email,那麼你會但願每十分鐘檢查一次,可是不用那麼精準。因此你能夠傳入60,告訴系統60秒的偏差是可接受的。
這樣有什麼意義呢?簡單來講,就是下降資源消耗。若是系統可讓cpu休息足夠長的時間,並 在每次醒來的時候執行一個任務集合,而不是不斷的醒來睡 去以執行任務,那麼系統會更高效。若是傳入一個比較大的leeway給你的計時器,意味着你容許系統拖延你的計時器來將計時器任務與其餘任務聯合起來一塊兒 執行。
總結
如今你知道怎樣使用GCD的dispatch source功能來監視文件描述符、計時器、聯結的用戶事件以及其餘相似的行爲。因爲dispatch source徹底與dispatch queue相集成,因此你可使用任意的dispatch queue。你能夠將一個dispatch source的句柄在主線程中執行、在全局隊列中併發執行、或者在用戶隊列中串行執行(執行時會將程序的其餘模塊的運算考慮在內)。
下一篇我會討論如何對dispatch queue進行掛起、恢復、重定目標操做;如何使用dispatch semaphore;如何使用GCD的一次性初始化功能。
GCD介紹(四): 完結
Dispatch Queue掛起
dispatch queue能夠被掛起和恢復。使用 dispatch_suspend函數來掛起,使用 dispatch_resume 函數來恢復。這兩個函數的行爲是如你所願的。另外,這兩個仍是也能夠用於dispatch source。
一個要注意的地方是,dispatch queue的掛起是block粒度的。換句話說,掛起一個queue並不會將當前正在執行的block掛起。它會容許當前執行的block執行完畢,而後後續的block再也不會被執行,直至queue被恢復。
還有一個注意點:從man頁上得來的:若是你掛起了一個queue或者source,那麼銷燬它以前,必須先對其進行恢復。
Dispatch Queue目標指定
全部的用戶隊列都有一個目標隊列概念。從本質上講,一個用戶隊列其實是不執行任何任務的,可是它會將任務傳遞給它的目標隊列來執行。一般,目標隊列是默認優先級的全局隊列。
用戶隊列的目標隊列能夠用函數 dispatch_set_target_queue來修 改。咱們能夠將任意 dispatch queue傳遞給這個函數,甚至能夠是另外一個用戶隊列,只要別構成循環就行。這個函數能夠用來設定用戶隊列的優先級。好比咱們能夠將用戶隊列的目標隊列設 定爲低優先級的全局隊列,那麼咱們的用戶隊列中的任務都會以低優先級執行。高優先級也是同樣道理。
有一個用途,是將用戶隊列的目標定爲main queue。這會致使全部提交到該用戶隊列的block在主線程中執行。這樣作來替代直接在主線程中執行代碼的好處在於,咱們的用戶隊列能夠單獨地被掛起 和恢復,還能夠被重定目標至一個全局隊列,而後全部的block會變成在全局隊列上執行(只要你確保你的代碼離開主線程不會有問題)。
還有一個用途,是將一個用戶隊列的目標隊列指定爲另外一個用戶隊列。這樣作能夠強制多個隊列相 互協調地串行執行,這樣足以構建一組隊列,經過掛起和暫 停那個目標隊列,咱們能夠掛起和暫停整個組。想象這樣一個程序:它掃描一組目錄而且加載目錄中的內容。爲了不磁盤競爭,咱們要肯定在同一個物理磁盤上同 時只有一個文件加載任務在執行。而但願能夠同時從不一樣的物理磁盤上讀取多個文件。要實現這個,咱們要作的就是建立一個dispatch queue結構,該結構爲磁盤結構的鏡像。
首先,咱們會掃描系統並找到各個磁盤,爲每一個磁盤建立一個用戶隊列。而後掃描文件系統,併爲 每一個文件系統建立一個用戶隊列,將這些用戶隊列的目標隊 列指向合適的磁盤用戶隊列。最後,每一個目錄掃描器有本身的隊列,其目標隊列指向目錄所在的文件系統的隊列。目錄掃描器枚舉本身的目錄併爲每一個文件向本身的 隊列提交一個block。因爲整個系統的創建方式,就使得每一個物理磁盤被串行訪問,而多個物理磁盤被並行訪問。除了隊列初始化過程,咱們根本不須要手動幹 預什麼東西。
信號量
dispatch的信號量是像其餘的信號量同樣的,若是你熟悉其餘多線程系統中的信號量,那麼這一節的東西再好理解不過了。
信號量是一個整形值而且具備一個初始計數值,而且支持兩個操做:信號通知和等待。當一個信號量被信號通知,其計數會被增長。當一個線程在一個信號量上等待時,線程會被阻塞(若是有必要的話),直至計數器大於零,而後線程會減小這個計數。
咱們使用函數 dispatch_semaphore_create 來建立dispatch信號量,使用函數 dispatch_semaphore_signal 來信號通知,使用函數 dispatch_semaphore_wait 來等待。這些函數的man頁有兩個很好的例子,展現了怎樣使用信號量來同步任務和有限資源訪問控制。
單次初始化
GCD還提供單詞初始化支持,這個與pthread中的函數 pthread_once 很類似。GCD提供的方式的優勢在於它使用block而非函數指針,這就容許更天然的代碼方式:
這個特性的主要用途是惰性單例初始化或者其餘的線程安全數據共享。典型的單例初始化技術看起來像這樣(線程安全的):
+ (id)sharedWhatever
{
static Whatever *whatever = nil;
@synchronized([Whatever class])
{
if(!whatever)
whatever = [[Whatever alloc] init];
}
return whatever;
}
這挺好的,可是代價比較昂貴;每次調用 +sharedWhatever 函數都會付出取鎖的代價,即便這個鎖只須要進行一次。確實有更風騷的方式來實現這個,使用相似雙向鎖或者是原子操做的東西,可是這樣挺難弄並且容易出錯。
使用GCD,咱們能夠這樣重寫上面的方法,使用函數 dispatch_once:
+ (id)sharedWhatever
{
static dispatch_once_t pred;
static Whatever *whatever = nil;
dispatch_once(&pred, ^{
whatever = [[Whatever alloc] init];
});
return whatever;
}
這個稍微比 @synchronized方法簡單些,而且GCD確保以更快的方式完成這些檢 測,它保證block中的代碼在任何線程經過 dispatch_once 調用以前被執行,但它不會強制每次調用這個函數都讓代碼進行同步控制。實際上,若是你去看這個函數所在的頭文件,你會發現 目前它的實現實際上是一個宏,進行了內聯的初始化測試,這意味着一般狀況下,你不用付出函數調用的負載代價,而且會有更少的同步控制負載。
結論
這一章,咱們介紹了dispatch queue的掛起、恢復和目標重定,以及這些功能的一些用途。另外,咱們還介紹瞭如何使用dispatch 信號量和單次初始化功能。到此,我已經完成了GCD如何運做以及如何使用的介紹。