1.隊列
串行隊列,串行隊列將任務以先進先出(FIFO)的順序來執(zhí)行,所以串行隊列經(jīng)常用來做訪問某些特定資源的同步處理。你可以也根據(jù)需要創(chuàng)建多個隊列,而這些隊列相對其他隊列都是并發(fā)執(zhí)行的。換句話說,如果你創(chuàng)建了4個串行隊列,每一個隊列在同一時間都只執(zhí)行一個任務,對這四個任務來說,他們是相互獨立且并發(fā)執(zhí)行的。如果需要創(chuàng)建串行隊列,一般用dispatch_queue_create這個方法來實現(xiàn),并指定隊列類型DISPATCH_QUEUE_SERIAL。
并發(fā)隊列,并發(fā)隊列雖然是能同時執(zhí)行多個任務,但這些任務仍然是按照先到先執(zhí)行(FIFO)的順序來執(zhí)行的。并發(fā)隊列會基于系統(tǒng)負載來合適地選擇并發(fā)執(zhí)行這些任務。并發(fā)隊列一般指的就是全局隊列(Global queue),進程中存在四個全局隊列:高、中(默認)、低、后臺四個優(yōu)先級隊列,可以調用dispatch_get_global_queue函數(shù)傳入優(yōu)先級來訪問隊列。當然我們也可以用dispatch_queue_create,并指定隊列類型DISPATCH_QUEUE_CONCURRENT,來自己創(chuàng)建一個并發(fā)隊列。
主隊列,與主線程功能相同。實際上,提交至main queue的任務會在主線程中執(zhí)行。main queue可以調用dispatch_get_main_queue()來獲得。因為main queue是與主線程相關的,所以這是一個串行隊列。和其它串行隊列一樣,這個隊列中的任務一次只能執(zhí)行一個。它能保證所有的任務都在主線程執(zhí)行,而主線程是唯一可用于更新 UI 的線程。
2.任務
同步任務,使用dispatch_sync將任務加入隊列。將同步任務加入串行隊列,會順序執(zhí)行,一般不這樣做并且在一個任務未結束時調起其它同步任務會死鎖。將同步任務加入并行隊列,會順序執(zhí)行,但是也沒什么意義。
異步任務,使用dispatch_async將任務加入隊列。將異步任務加入串行隊列,會順序執(zhí)行,并且不會出現(xiàn)死鎖問題。將異步任務加入并行隊列,會并行執(zhí)行多個任務,這也是我們最常用的一種方式。
3.GCD常見的用法和應用場景
3.1 dispatch_async(常見的應用場景是異步處理耗時的操作,然后耗時操作處理完畢后,使用主線程更新UI)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
// 一個異步的任務,例如網(wǎng)絡請求,耗時的文件操作等等
...
dispatch_async(dispatch_get_main_queue(), ^{
// UI刷新
...
});
});
3.2 dispatch_after (常用的應用場景是延時調用)
dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
// 在queue里面延遲執(zhí)行的一段代碼
...
});
3.3 dispatch_once (常用于單例的創(chuàng)建,只創(chuàng)建一次)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行一次的任務
...
});
3.4 dispatch_group (GCD組,把一組任務提交到隊列中,多個請求完畢后才處理事情,如多個網(wǎng)絡請求完畢會,才去更新UI)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
// 異步任務1
});
dispatch_group_async(group, queue, ^{
// 異步任務2
});
// 等待group中多個異步任務執(zhí)行完畢,做一些事情,介紹兩種方式
// 方式1(不好,會卡住當前線程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
...
// 方式2(比較好)
dispatch_group_notify(group, mainQueue, ^{
// 任務完成后,在主隊列中做一些操作
...
});
3.5 dispatch_barrier_async(和dispatch_group類似,dispatch_barrier也是異步任務間的一種同步方式,可以在比如文件的讀寫操作時使用,保證讀操作的準確性。另外,有一點需要注意,dispatch_barrier_sync和dispatch_barrier_async只在自己創(chuàng)建的并發(fā)隊列上有效,在全局(Global)并發(fā)隊列、串行隊列上,效果跟dispatch_(a)sync效果一樣)
// dispatch_barrier_async的作用可以用一個詞概括--承上啟下,它保證此前的任務都先于自己執(zhí)行,此后的任務也遲于自己執(zhí)行。本例中,任務4會在任務1、2、3都執(zhí)行完之后執(zhí)行,而任務5、6會等待任務4執(zhí)行完后執(zhí)行。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 任務1
...
});
dispatch_async(queue, ^{
// 任務2
...
});
dispatch_async(queue, ^{
// 任務3
...
});
dispatch_barrier_async(queue, ^{
// 任務4
...
});
dispatch_async(queue, ^{
// 任務5
...
});
dispatch_async(queue, ^{
// 任務6
...
});
3.6 dispatch_apply(dispatch_apply有什么用呢,因為dispatch_apply并行的運行機制,效率一般快于for循環(huán)的類串行機制(在for一次循環(huán)中的處理任務很多時差距比較大)。比如這可以用來拉取網(wǎng)絡數(shù)據(jù)后提前算出各個控件的大小,防止繪制時計算,提高表單滑動流暢性,如果用for循環(huán),耗時較多,并且每個表單的數(shù)據(jù)沒有依賴關系,所以用dispatch_apply比較好)
// for循環(huán)做一些事情,輸出0123456789
for (int i = 0; i < 10; i ++) {
NSLog(@"%d", i);
}
// dispatch_apply替換(當且僅當處理順序對處理結果無影響環(huán)境),輸出順序不定,比如1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函數(shù)說明
*
* @brief dispatch_apply函數(shù)是dispatch_sync函數(shù)和Dispatch Group的關聯(lián)API
* 該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等到全部的處理執(zhí)行結束
*
* @param 10 指定重復次數(shù) 指定10次
* @param queue 追加對象的Dispatch Queue
* @param index 帶有參數(shù)的Block, index的作用是為了按執(zhí)行的順序區(qū)分各個Block
*
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
3.7 dispatch_suspend和dispatch_resume(隊列的暫停和恢復,已添加到隊列中沒有執(zhí)行的任務不會執(zhí)行,直至等到線程恢復才會繼續(xù)執(zhí)行)
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暫停隊列queue
dispatch_resume(queue); //恢復隊列queue
3.8 dispatch_semaphore_signal
dispatch_semaphore 信號量基于計數(shù)器的一種多線程同步機制。在多個線程訪問共有資源時候,會因為多線程的特性而引發(fā)數(shù)據(jù)出錯的問題。
// dispatch_semaphore_signal有兩類用法:a、解決同步問題;b、解決有限資源訪問(資源為1,即互斥)問題。
// dispatch_semaphore_wait,若semaphore計數(shù)為0則等待,大于0則使其減1。
// dispatch_semaphore_signal使semaphore計數(shù)加1。
// a、同步問題:輸出肯定為1、2、3。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
// 任務1
dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
NSLog(@"1\n");
dispatch_semaphore_signal(semaphore2);
dispatch_semaphore_signal(semaphore1);
});
dispatch_async(queue, ^{
// 任務2
dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
NSLog(@"2\n");
dispatch_semaphore_signal(semaphore3);
dispatch_semaphore_signal(semaphore2);
});
dispatch_async(queue, ^{
// 任務3
dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
NSLog(@"3\n");
dispatch_semaphore_signal(semaphore3);
});
// b、有限資源訪問問題:for循環(huán)看似能創(chuàng)建100個異步任務,實質由于信號限制,最多創(chuàng)建10個異步任務。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
// 任務
...
dispatch_semaphore_signal(semaphore);
});
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
如果semaphore計數(shù)大于等于1.計數(shù)-1,返回,程序繼續(xù)運行。
如果計數(shù)為0,則等待。
這里設置的等待時間是一直等待。
dispatch_semaphore_signal(semaphore);
計數(shù)+1.
在這兩句代碼中間的執(zhí)行代碼,每次只會允許一個線程進入,這樣就有效的保證了在多線程環(huán)境下,只能有一個線程進入。
3.9 dispatch_set_context、dispatch_get_context和dispatch_set_finalizer_f(dispatch_set_context可以為隊列添加上下文數(shù)據(jù),但是因為GCD是C語言接口形式的,所以其context參數(shù)類型是“void *”。需使用上述abc三種方式創(chuàng)建context,并且一般結合dispatch_set_finalizer_f使用,回收context內存)
// dispatch_set_context、dispatch_get_context是為了向隊列中傳遞上下文context服務的。
// dispatch_set_finalizer_f相當于dispatch_object_t的析構函數(shù)。
// 因為context的數(shù)據(jù)不是foundation對象,所以arc不會自動回收,一般在dispatch_set_finalizer_f中手動回收,所以一般講上述三個方法綁定使用。
- (void)test
{
// 幾種創(chuàng)建context的方式
// a、用C語言的malloc創(chuàng)建context數(shù)據(jù)。
// b、用C++的new創(chuàng)建類對象。
// c、用Objective-C的對象,但是要用__bridge等關鍵字轉為Core Foundation對象。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
if (queue) {
// "123"即為傳入的context
dispatch_set_context(queue, "123");
dispatch_set_finalizer_f(queue, &xigou);
}
dispatch_async(queue, ^{
char *string = dispatch_get_context(queue);
NSLog(@"%s", string);
});
}
// 該函數(shù)會在dispatch_object_t銷毀時調用。
void xigou(void *context)
{
// 釋放context的內存(對應上述abc)
// a、CFRelease(context);
// b、free(context);
// c、delete context;
}
4. 常見的死鎖
4.1 dispatch_sync
// 假設這段代碼執(zhí)行于主隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 在主隊列添加同步任務
dispatch_sync(mainQueue, ^{
// 任務
...
});
// 在串行隊列添加同步任務
dispatch_sync(serialQueue, ^{
// 任務
...
dispatch_sync(serialQueue, ^{
// 任務
...
});
};
4.2 dispatch_apply
// 因為dispatch_apply會卡住當前線程,內部的dispatch_apply會等待外部,外部的等待內部,所以死鎖。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
// 任務
...
dispatch_apply(10, queue, ^(size_t) {
// 任務
...
});
});
4.3 dispatch_barrier
dispatch_barrier_sync在串行隊列和全局并行隊列里面和dispatch_sync同樣的效果,所以需考慮同dispatch_sync一樣的死鎖問題。