iOS GCD使用詳解

Grand Central Dispath(GCD):是iOS 4和OS X Snow Leopard 開始引入的新多線程編程功能。

GCD是異步執(zhí)行任務(wù)的技術(shù)之一。

我們只需要定義想執(zhí)行的任務(wù)并追加到適當?shù)腄ispatch Queue中,GCD就能生成必要的線程并計劃執(zhí)行任務(wù)。

這里的線程管理是作為系統(tǒng)的一部分來實現(xiàn)的。

在GCD出現(xiàn)之前,Cocoa框架在NSObject類中提供了 performSelectorInBackground: withObject:方法和 performSelectorOnMainThread: withObject: waitUntilDone:方法等這樣簡單的多線程技術(shù)。

而GCD出現(xiàn)后,我們就可以通過 dispatch_async(queue, ^{ });和 dispatch_async(dispatch_get_main_queue(), ^{ });在后臺和主線程中完成相應(yīng)操作。

相比之下,GCD代碼更為簡潔,并且由于是系統(tǒng)管理線程,執(zhí)行效率更高。

一個CPU核一次只能執(zhí)行一個命令。CPU核執(zhí)行的命令列也是一條無分叉的路徑,即使地址分散在各處。

這種無分叉路徑不只1條,存在多條時即為“多線程”。

多線程可能是一個CPU核在多個線程之間來回切換,看上去像一個CPU核并列地執(zhí)行多個線程一樣。也有可能是真的具有多個CPU核并行執(zhí)行多個線程。

多線程可能發(fā)生的問題:

1、多個線程同時更新相同的資源,導(dǎo)致數(shù)據(jù)競爭;

2、多個線程相互等待,形成死鎖;

3、使用太多線程會消耗大量內(nèi)存;

但是多線程也必須使用,因為多線程可以保證應(yīng)用程序的響應(yīng)性能。但是長時間的處理不能在主線程中執(zhí)行,需在其他線程中執(zhí)行。因為在主線程中長時間處理會妨礙主循環(huán)RunLoop的執(zhí)行,從而導(dǎo)致不能更新用戶界面,應(yīng)用程序的畫面長時間停滯等問題。

GCD的API

“定義想執(zhí)行的任務(wù)并追加到適當?shù)腄ispatch Queue中”。其中,Dispatch Queue就是執(zhí)行處理任務(wù)的等待隊列,而Block中就是待執(zhí)行的任務(wù)。

在執(zhí)行處理時,會用到兩種隊列:

1、Serial Dispatch Queue: 等待前一個任務(wù)執(zhí)行完畢再開始處理下一個任務(wù);

2、Concurrent Dispatch Queue: 不等待前一個任務(wù)處理完就開始執(zhí)行下一個任務(wù);

關(guān)于串行/并行,同步/異步的理解:

異步串行:就是另起一個新的線程,在這個新線程中,按照任務(wù)加入的順序依次執(zhí)行完所有的任務(wù);

異步并行:就是創(chuàng)建多個新的線程,同時執(zhí)行多個任務(wù),因此,這個方式執(zhí)行完任務(wù)的順序是隨機的;

同步并行:就是不創(chuàng)建新的線程,因此,即使是并行的隊列,也必須按照任務(wù)添加的順序依次執(zhí)行完,才能繼續(xù)后面的任務(wù);

同步串行:和同步并行一樣,不能開啟新線程,只能按隊列中任務(wù)的順序來依次執(zhí)行。

XNU內(nèi)核(iOS和OS X的核心)決定了可以并行的線程最大數(shù);XNU內(nèi)核通過Concurrent Dispatch Queue來管理并執(zhí)行多線程。

獲取Dispatch Queue的方法:

1、通過 GCD的API:dispatch_queue_create生成Dispatch Queue。

2、獲取系統(tǒng)標準提供的Dispatch Queue;

1、dispatch_queue_create

dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)

其中,label表示queue的名稱,通常使用應(yīng)用程序ID的逆序全稱域名來表示。這樣也方便程序在出現(xiàn)crash時,方便定位和調(diào)試。

第二個參數(shù),attr用來指明要生成的隊列類型,NULL和DISPATCH_QUEUE_SERIAL都表示生成串行隊列,DISPATCH_QUEUE_CONCURRENT表示生成并行隊列。

dispatch_queue_create函數(shù)可以生成任意個Dispatch Queue。

當生成多個串行Dispatch Queue時,各個串行隊列分別對應(yīng)生成自己的線程,來并行執(zhí)行,但是每個串行隊列同時只能執(zhí)行一個處理。

如果生成過多的串行隊列,就會生成多個線程,因此會消耗大量的內(nèi)存,引起大量的上下文切換,大幅降低系統(tǒng)的響應(yīng)性能。

因此只在避免多線程更新相同資源導(dǎo)致的數(shù)據(jù)競爭問題時,使用Serial Dispatch Queue。因此Serial Dispatch Queue生成的數(shù)量僅需所需要的數(shù)量。

在不存在數(shù)據(jù)競爭想并行處理問題時,可以使用Concurrent Dispatch Queue。對于并行隊列,不管生成多少,XNU內(nèi)核只使用有效管理的線程,因此不會出現(xiàn)串行隊列大量消耗內(nèi)存的問題。

因為Dispatch Queue的dispatch_queue_t類型,不是OC對象,不能自動管理其內(nèi)存,因此通過dispatch_queue_create函數(shù)生成的Dispatch Queue需要我們通過dispatch_retain和dispatch_release函數(shù)來管理內(nèi)存。在ARC中不需要使用dispatch_retain和dispatch_release函數(shù)。

即使在 dispatch_async后立即通過dispatch_release釋放dispatch_queue_create創(chuàng)建的隊列,也不會有任何問題。

因為,在dispatch_async函數(shù)中追加Block到Dispatch Queue中后,該Block就通過dispatch_retain函數(shù)持有了Dispatch Queue,因此,Dispatch Queue不會被立即廢棄,Block也可以執(zhí)行,當Block執(zhí)行結(jié)束后,這時誰都不持有Dispatch Queue,Dispatch Queue被廢棄了,因此,會通過dispatch_release函數(shù)釋放掉Dispatch Queue。

在其他的GCD的API中,名稱中含有“create”的API生成的對象,都有必要通過dispatch_retain函數(shù)持有,和dispatch_release函數(shù)在不需要時釋放。在ARC中不需要使用dispatch_retain和dispatch_release函數(shù)。

2、獲取系統(tǒng)標準提供的Dispatch Queue

通過獲取系統(tǒng)提供的Main Dispatch Queue 和Global Dispatch Queue。

Main Dispatch Queue是主線程隊列,是一個串行隊列。用來執(zhí)行一些界面更新之類的處理。

Global Dispatch Queue是全局隊列,是一個并行隊列。Global Dispatch Queue分為四個優(yōu)先級,分別是:高優(yōu)先級,默認優(yōu)先級,低優(yōu)先級,后臺優(yōu)先級。

系統(tǒng)隊列獲取的方法:

//Main Dispatch Queue

dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

//高優(yōu)先級Global Dispatch Queue

dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

//默認優(yōu)先級Global Dispatch Queue

dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//低優(yōu)先級Global Dispatch Queue

dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

//后臺優(yōu)先級Global Dispatch Queue

dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

對Main Dispatch Queue 和Global Dispatch Queue使用dispatch_retain和dispatch_release函數(shù)不會產(chǎn)生任何作用。在ARC中不需要使用dispatch_retain和dispatch_release函數(shù)。

dispatch_set_target_queue

1、變更優(yōu)先級:

使用dispatch_queue_create生成的隊列,不管是Serial Dispatch Queue還是Concurrent Dispatch Queue,都是和默認優(yōu)先級的Global Dispatch Queue相同執(zhí)行優(yōu)先級的。

如果需要變更隊列的優(yōu)先級,就需要使用 dispatch_set_target_queue(<#dispatch_object_t _Nonnull object#>, <#dispatch_queue_t _Nullable queue#>)。

其中,第一個參數(shù):是待變更優(yōu)先級的隊列。

第二個參數(shù)是:要使用的優(yōu)先級的Global Dispatch Queue。

但是,待變更的隊列不能是Main Dispatch Queue 和Global Dispatch Queue,因為不知道會發(fā)生什么狀況。

2、變更執(zhí)行階層:

并行——>串行:變更執(zhí)行階層。

如果多個Serial Dispatch Queue(Queue1,Queue2,Queue3…)都使用dispatch_set_target_queue函數(shù)指定目標為一個新的Serial Dispatch Queue(Queue A),那么原先本應(yīng)并行執(zhí)行的多個Serial Dispatch Queue(Queue1,Queue2,Queue3…),在目標Serial Dispatch Queue(Queue A)上,就會串行執(zhí)行,也就是,同時只能執(zhí)行一個處理。

這種變更執(zhí)行階層,在某些情況下可以防止并行處理。

dispatch_after

1、 dispatch_after表示在指定時間后執(zhí)行處理。但可能不是嚴格按照指定時間執(zhí)行,而是稍有延遲。

dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)

第一個參數(shù):when是指定時間的dispatch_time_t類型;

第二個參數(shù)是:要追加處理的隊列;

第三個參數(shù)是:要處理的Block;

dispatch_time_t類型的時間可以通過dispatch_time或 dispatch_walltime獲得。

dispatch_time是通過計算相對時間獲取時間;

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 200ull * NSEC_PER_SEC);

表示獲取從現(xiàn)在起200毫秒以后的時間。

dispatch_walltime是通過計算絕對時間獲取的時間。

dispatch_walltime是從struct timespec類型獲取dispatch_time_t時間類型;

Dispatch Group

如果我們想在Dispatch Queue中的多個處理全部執(zhí)行完成時,做相應(yīng)的結(jié)束處理。

對于Serial Dispatch Queue來說,我們只需要將結(jié)束處理追加在隊列的最后就可以了。

但是對于Concurrent Dispatch Queue來說,每個任務(wù)執(zhí)行完成的時機是不確定的,因此追加最后的結(jié)束處理也比較困難。對于這種情況,我們就可以使用Dispatch Group。

demo如下:

dispatch_group_t group = dispatch_group_create();

? ? dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);


? ? dispatch_group_async(group, queue, ^{NSLog(@"1");});

? ? dispatch_group_async(group, queue, ^{NSLog(@"2");});

? ? dispatch_group_async(group, queue, ^{NSLog(@"3");});


? ? dispatch_group_notify(group, queue, ^{NSLog(@"4");});

同一段代碼,多打印幾次日志,

日志1
日志2
日志3

可以發(fā)現(xiàn):

前三個Block輸出的日志順序是不確定的,但是最后一個block的輸出次序一定是在最后,也就是隊列中的前三個Block執(zhí)行完畢后再執(zhí)行。

dispatch_group_async的用法與 dispatch_async函數(shù)基本相同,都可以追加Block到Dispatch Queue中,與dispatch_async不同的是,dispatch_group_async多了一個參數(shù)Dispatch Group,并且指定的Block是屬于對應(yīng)的Dispatch Group的。

在Dispatch Group中也可以使用 dispatch_group_wait函數(shù)來在指定時間結(jié)束或全部處理結(jié)束時執(zhí)行。

dispatch_group_wait(<#dispatch_group_t _Nonnull group#>, <#dispatch_time_t timeout#>)

中,第一個參數(shù)就是要監(jiān)測的group,第二個參數(shù)就是需等待的dispatch_time_t時間類型。

dispatch_group_wait函數(shù)一旦調(diào)用,就會處于調(diào)用而不返回狀態(tài),因此,調(diào)用其的線程就會處于停止狀態(tài),只要等group執(zhí)行結(jié)束或者指定時間到達時,才會返回。

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 200ull * NSEC_PER_SEC);

long result = dispatch_group_wait(group, time);

if (result == 0)

{

//返回值result為0時,表示經(jīng)過指定時間time后,Dispatch Group中的處理依然沒有執(zhí)行結(jié)束。

}

else

{

//返回值result不為0時,表示Dispatch Group中的處理已經(jīng)全部執(zhí)行結(jié)束。

}

返回值result不為0時,表示經(jīng)過指定時間time后,Dispatch Group中的處理依然沒有執(zhí)行結(jié)束。返回值result為0時,表示Dispatch Group中的處理已經(jīng)全部執(zhí)行結(jié)束。

dispatch_barrier_async

dispatch_barrier_async函數(shù)結(jié)合 dispatch_queue_create函數(shù)生成的Concurrent Dispatch Queue一起,可以使追加在dispatch_barrier_async之前的處理全部執(zhí)行結(jié)束后,再將dispatch_barrier_async中追加在Queue里的處理執(zhí)行。dispatch_barrier_async中的處理執(zhí)行完畢后,再恢復(fù)dispatch_async的一般動作,執(zhí)行dispatch_barrier_async之后所追加的處理。

dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, block1);

dispatch_async(queue, block2);

dispatch_async(queue, block3);

dispatch_barrier_async(queue, block4);

dispatch_async(queue, block5);

dispatch_async(queue, block6);

dispatch_async(queue, block7);

使用dispatch_barrier_async函數(shù)和 dispatch_queue_create函數(shù)生成的Concurrent Dispatch Queue可以實現(xiàn)高效率的安全的數(shù)據(jù)庫訪問和文件訪問。

dispatch_sync

dispatch_sync是等待處理執(zhí)行結(jié)束后,再繼續(xù)。

一旦調(diào)用,在指定處理結(jié)束之前,該函數(shù)不會返回。效果等同于dispatch_group_wait函數(shù)。

dispatch_sync和主線程隊列Main Dispatch Queue,以及Serial Dispatch Queue一起使用時,很容易形成死鎖。需謹慎使用。

比如,下面的和Main Dispatch Queue一起使用:

dispatch_queue_t queue = dispatch_get_main_queue();

? ? dispatch_async(queue, ^{

? ? ? ? dispatch_sync(queue, ^{NSLog(@"hello");});

? ? });

? ? NSLog(@"what");

在執(zhí)行這幾行代碼時,會在第三行代碼處,報Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)死鎖的錯誤。因此最后一行代碼時執(zhí)行不到的。

同樣的,在和Serial Dispatch Queue一起使用時,也容易發(fā)生類似問題:

dispatch_queue_t queue = dispatch_queue_create("queue", NULL);

? ? dispatch_async(queue, ^{

? ? ? ? dispatch_sync(queue, ^{NSLog(@"hello");});

? ? });

? ? NSLog(@"what");

在執(zhí)行這幾行代碼時,會在第三行代碼處,報Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)死鎖的錯誤。因此最后一行代碼時執(zhí)行不到的。

dispatch_apply

dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t? _Nonnull queue#>, <#^(size_t)block#>)

第一個參數(shù)iterations:重復(fù)的次數(shù);

第二個參數(shù)queue:要追加的隊列;

第三個參數(shù)block:要追加的處理;

demo如下:

NSArray *array = [NSArray arrayWithObjects:@0, @1, @2, @3, nil];

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply([array count], queue, ^(size_t index){

? ? NSLog(@"%@",[array objectAtIndex:index]);

});

根據(jù)打印的日志

dispatch_apply日志

可以看出, dispatch_apply是非同步的。但是 dispatch_apply會等待追加的處理全部執(zhí)行完畢。

掛起指定隊列:

dispatch_suspend(queue);

恢復(fù)指定隊列:

dispatch_resume(queue);

dispatch_semaphore_wait

dispatch_semaphore_wait(<#dispatch_semaphore_t? _Nonnull dsema#>, <#dispatch_time_t timeout#>)

是比Serial Dispatch Queue和dispatch_barrier_async更細粒度的避免數(shù)據(jù)競爭的排他控制方法。

dispatch_semaphore_wait和dispatch_group_wait一樣,可以設(shè)定等待時間,當指定時間到達時,或者Dispatch Semaphore的計數(shù)值大于等于1時返回。返回時會將Dispatch Semaphore的計數(shù)值減1。

當Dispatch Semaphore的計數(shù)值大于等于1時或者在指定等待時間內(nèi)Dispatch Semaphore的計數(shù)值大于等于1時,返回值為0。當指定時間到時,Dispatch Semaphore的計數(shù)值為0的話,返回值不為0。

當dispatch_semaphore_wait返回值為0時,就可安全的執(zhí)行排他處理。處理結(jié)束后,使用 dispatch_semaphore_signal函數(shù)將Dispatch Semaphore的計數(shù)值加1。

dispatch_once

dispatch_once函數(shù)保證程序中只執(zhí)行一次處理的函數(shù)。通常用在生成單例對象時。

總結(jié)

使用GCD要比使用NSThread和pthreads這些一般的多線程編程API更好。因為任何由程序員編寫的管理線程的代碼,在性能方面都比不上iOS和OS X的核心XNU內(nèi)核級實現(xiàn)的GCD。因此我們可以盡量多使用GCD或者使用Cocoa框架中GCD的NSOperation類的API。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容