多線(xiàn)程有幾個(gè)重要的概念,任務(wù)、隊(duì)列、線(xiàn)程。
任務(wù):是指執(zhí)行什么樣的操作,在GCD中就是block。
隊(duì)列:用來(lái)存放任務(wù),分為串行隊(duì)列和并行隊(duì)列。放在串行隊(duì)列中的任務(wù)要等到正在執(zhí)行的任務(wù)執(zhí)行完后才會(huì)被執(zhí)行;放在并行隊(duì)列中的任務(wù)不用等到正在執(zhí)行的任務(wù)執(zhí)行完就可以被執(zhí)行。
線(xiàn)程:執(zhí)行任務(wù)需要線(xiàn)程,線(xiàn)程從隊(duì)列中以先進(jìn)先出(FIFO)的方式取出任務(wù)執(zhí)行,線(xiàn)程一次只能執(zhí)行一個(gè)任務(wù)。
這三者的關(guān)系就是:線(xiàn)程從隊(duì)列中取任務(wù)執(zhí)行。
GCD中的隊(duì)列:
dispatch_main_queue 主隊(duì)列,程序啟動(dòng)時(shí)與主線(xiàn)程一起由系統(tǒng)自動(dòng)創(chuàng)建,UI操作都放在主隊(duì)列中
dispatch_get_global_queu 全局并發(fā)隊(duì)列,由系統(tǒng)定義,調(diào)用函數(shù)dispatch_get_global_queue(identifier: Int, flags: Uint)獲取。identifier以前叫做優(yōu)先級(jí),現(xiàn)在稱(chēng)為服務(wù)質(zhì)量,現(xiàn)在傳入優(yōu)先級(jí)的宏定義也可以,目測(cè)優(yōu)先級(jí)的界限并不明顯,所以平時(shí)直接傳入0,見(jiàn)下面對(duì)應(yīng)關(guān)系。flags是系統(tǒng)保留的一個(gè)參數(shù),傳入0即可。
DISPATCH_QUEUE_PRIORITY_HIGH QOS_CLASS_USER_INITIATED 2
DISPATCH_QUEUE_PRIORITY_DEFAULT QOS_CLASS_DEFAULT 0
DISPATCH_QUEUE_PRIORITY_LOW QOS_CLASS_UTILITY -2
DISPATCH_QUEUE_PRIORITY_BACKGROUND QOS_CLASS_BACKGROUND INT16_MIN
對(duì)于同一優(yōu)先級(jí),獲取到的全局并發(fā)隊(duì)列是同一個(gè); 不同優(yōu)先級(jí),獲取到的全局并發(fā)隊(duì)列也不同??聪旅娅@取全局并發(fā)隊(duì)列
NSLog(@"%@",dispatch_get_global_queue(0, 0));
NSLog(@"%@",dispatch_get_global_queue(0, 0));
NSLog(@"%@",dispatch_get_global_queue(2, 0));
NSLog(@"%@",dispatch_get_global_queue(2, 0));

dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)
開(kāi)發(fā)者自己創(chuàng)建的隊(duì)列,參數(shù)label是該隊(duì)列的名字,可以通過(guò)
dispatch_queue_get_label(<#dispatch_queue_t _Nullable queue#>)
獲取。參數(shù)attr傳入DISPATCH_QUEUE_SERIAL或nil返回串行隊(duì)列,傳入DISPATCH_QUEUE_CONCURRENT返回并行隊(duì)列。
dispatch_queue_create 創(chuàng)建的優(yōu)先級(jí)與全局并發(fā)隊(duì)列的默認(rèn)優(yōu)先級(jí)是同一級(jí)別,也就是說(shuō)創(chuàng)建的隊(duì)列的優(yōu)先級(jí)為默認(rèn)優(yōu)先級(jí)。至于怎么改變它的優(yōu)先級(jí)需要調(diào)用dispatch_set_target_queue,這個(gè)函數(shù)后面描述。
調(diào)度函數(shù)
dispatch_sync 將任務(wù)提交到相應(yīng)隊(duì)列,同步執(zhí)行,不開(kāi)新線(xiàn)程
dispatch_async 將任務(wù)提交到相應(yīng)隊(duì)列,異步執(zhí)行,如果從主隊(duì)列取任務(wù),不開(kāi)新線(xiàn)程,在主線(xiàn)程中執(zhí)行;其他隊(duì)列,開(kāi)新線(xiàn)程執(zhí)行
現(xiàn)在來(lái)看看調(diào)度函數(shù)與各種隊(duì)列的組合后的情況:
1、dispatch_sync 與 串行隊(duì)列:
(1)在串行隊(duì)列queue中調(diào)用該函數(shù)、且該函數(shù)是從串行隊(duì)列queue中去任務(wù)執(zhí)行,就會(huì)出現(xiàn)線(xiàn)程死鎖。 例如下面兩種情況:
在主線(xiàn)程中調(diào)用
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"在主線(xiàn)程中調(diào)用dispatch_sync,并且該函數(shù)第一個(gè)參數(shù)傳入的是主隊(duì)列,則會(huì)出現(xiàn)線(xiàn)程死鎖");
});
在同步執(zhí)行任務(wù)的子線(xiàn)程中調(diào)用
dispatch_queue_t queue = dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
/*
其他代碼
*/
dispatch_sync(queue, ^{
NSLog(@"queue是串行隊(duì)列就會(huì)出現(xiàn)線(xiàn)程死鎖");
});
});
(2)該串行隊(duì)列與當(dāng)前隊(duì)列不同,不會(huì)出現(xiàn)線(xiàn)程死鎖。在當(dāng)前線(xiàn)程中同步執(zhí)行。dispatch_sync執(zhí)行完后它后面的代碼才會(huì)執(zhí)行。
2、dispatch_sync 與 并行隊(duì)列:
因?yàn)閐ispatch_sync不開(kāi)線(xiàn)程,并且線(xiàn)程一次只能執(zhí)行一個(gè)任務(wù),所以依然是,dispatch_sync執(zhí)行完后它后面的代碼才會(huì)執(zhí)行。
3、dispatch_async 與 串行隊(duì)列:
(1)主隊(duì)列,不開(kāi)新線(xiàn)程,任務(wù)將在主線(xiàn)程中執(zhí)行。但不會(huì)馬上執(zhí)行,而是將任務(wù)從棧copy到堆,等待主隊(duì)列中棧區(qū)的任務(wù)執(zhí)行完,再執(zhí)行堆區(qū)的任務(wù)。因此
dispatch_async(dispatch_get_main_queue(), ^{
/*
在主線(xiàn)程執(zhí)行任務(wù)
*/
});
有兩個(gè)作用:1)子線(xiàn)程處理耗時(shí)操作后回到主線(xiàn)程處理UI; 2)延時(shí)。
(2)開(kāi)發(fā)者創(chuàng)建的串行隊(duì)列,開(kāi)新線(xiàn)程同步執(zhí)行。
4、dispatch_async 與 并發(fā)隊(duì)列:
開(kāi)新線(xiàn)程并發(fā)執(zhí)行任務(wù),至于開(kāi)多少條線(xiàn)程由系統(tǒng)決定??聪旅娴拇a以及運(yùn)行結(jié)果:
for (int i = 0; i < 20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"線(xiàn)程%@執(zhí)行第%d個(gè)任務(wù)",[NSThread currentThread],i);
});
}

dispatch_after 延時(shí)操作,調(diào)用函數(shù)
dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
參數(shù)when 指定時(shí)間,傳入DISPATCH_TIME_NOW,效果等同于dispatch_async;傳入DISPATCH_TIME_FOREVER,任務(wù)將永不執(zhí)行;所以一般傳入dispatch_time(DISPATCH_TIME_NOW, (int64_t)(t * NSEC_PER_SEC))
t*NSEC_PER_SEC代表t秒。
參數(shù)queue可以是任意隊(duì)列,一般傳入主隊(duì)列。
dispatch_group 調(diào)度組
幾個(gè)任務(wù)并發(fā)執(zhí)行,并且等到都執(zhí)行完后再做其他操作,就可調(diào)使用該函數(shù)。例如異步下載小說(shuō)A、B、C,下載完后提示用戶(hù),代碼如下:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"下載小說(shuō)A");
});
dispatch_group_async(group, queue, ^{
NSLog(@"下載小說(shuō)B");
});
dispatch_group_async(group, queue, ^{
NSLog(@"下載小說(shuō)C");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有小說(shuō)下載完成");
});

也可將
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有小說(shuō)下載完成");
});
替換成
BOOL flag = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
if (flag) {
NSLog(@"所有小說(shuō)下載完成");
} else {
NSLog(@"小說(shuō)下載超時(shí)");
}
dispatch_group_wait的第二個(gè)參數(shù)可傳入指定時(shí)間。
dispatch_once 一次性操作
調(diào)用函數(shù)
dispatch_once(<#dispatch_once_t * _Nonnull predicate#>, <#^(void)block#>)
參數(shù)predicate 是用來(lái)標(biāo)識(shí)block是否執(zhí)行的指針,必須是全局或靜態(tài)變量,并且該指針?biāo)赶虻膮^(qū)域的初始值為0,當(dāng)任務(wù)執(zhí)行完畢,系統(tǒng)會(huì)將其值置為-1。
這個(gè)函數(shù)用于初始化全局?jǐn)?shù)據(jù),并且保證線(xiàn)程安全,常用于OC中的單例模式。
dispatch_apply dispatch_sync 與 dispatch_group 的結(jié)合
調(diào)用函數(shù)
dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t _Nonnull queue#>, <#^(size_t)block#>)
參數(shù) iterations 將任務(wù)循環(huán)添加到指定隊(duì)列的次數(shù); block的參數(shù)是循環(huán)下標(biāo)(從0開(kāi)始)
調(diào)用該函數(shù)會(huì)將queue中的任務(wù)執(zhí)行完才執(zhí)行它后面的代碼。queue如果是串行隊(duì)列,queue中的任務(wù)會(huì)在當(dāng)前線(xiàn)程中同步執(zhí)行;queue如果是并行隊(duì)列,當(dāng)前線(xiàn)程會(huì)取出queue中的第一個(gè)任務(wù)執(zhí)行,然后開(kāi)新線(xiàn)程執(zhí)行后面的任務(wù),所開(kāi)線(xiàn)程個(gè)數(shù)由系統(tǒng)決定。因?yàn)閐ispatch_apply具有dispatch_sync的特征,所以如果queue是串行隊(duì)列并且與當(dāng)前的隊(duì)列是同一個(gè)對(duì)象時(shí),就會(huì)出現(xiàn)線(xiàn)程死鎖。
dispatch_apply主要用于異步并發(fā)處理數(shù)據(jù),并且處理完后統(tǒng)一操作。所以dispatch_apply一般在子線(xiàn)程中調(diào)用。當(dāng)然,像這樣的操作用dispatch_group也可以實(shí)現(xiàn)。但是,思考這種情況,如果對(duì)象數(shù)組,里面所有元素需要并發(fā)執(zhí)行某種操作,并且都執(zhí)行完之后要統(tǒng)一做處理,這時(shí)如果用dispatch_group,則需要for in 遍歷數(shù)組,用dispatch_apply則可以省去這種遍歷。代碼如下:
dispatch_async(queue, ^{
dispatch_apply(arr.count, dispatch_get_global_queue(0, 0), ^(size_t i) {
NSLog(@"%@",arr[i]);
});
});
dispatch_barrier_async 在處理數(shù)據(jù)讀取時(shí),為了避免數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題,寫(xiě)入操作與寫(xiě)入操作或讀取操作不能并發(fā)執(zhí)行。針對(duì)這個(gè)例子,可以使用dispatch_barrier_async將寫(xiě)入操作提交到并發(fā)隊(duì)列中,此時(shí)被提交的任務(wù)暫不執(zhí)行,當(dāng)他前面的任務(wù)執(zhí)行完畢時(shí)在執(zhí)行該任務(wù),等到該任務(wù)執(zhí)行完畢,后面提交的才執(zhí)行。需要注意的是指定的queue應(yīng)該是通過(guò)dispatch_queue_create創(chuàng)建的,系統(tǒng)定義的全局并發(fā)隊(duì)列無(wú)效。示例代碼如下:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"讀取操作1");
});
dispatch_async(queue, ^{
NSLog(@"讀取操作2");
});
dispatch_async(queue, ^{
NSLog(@"讀取操作3");
});
dispatch_barrier_async(queue, ^{
NSLog(@"寫(xiě)入操作1");
});
dispatch_async(queue, ^{
NSLog(@"讀取操作4");
});
dispatch_async(queue, ^{
NSLog(@"讀取操作5");
});
dispatch_async(queue, ^{
NSLog(@"讀取操作6");
});
dispatch_barrier_async(queue, ^{
NSLog(@"寫(xiě)入操作2");
});
dispatch_async(queue, ^{
NSLog(@"讀取操作7");
});
dispatch_async(queue, ^{
NSLog(@"讀取操作8");
});

與之對(duì)應(yīng)的是
dispatch_barrier_sync,該函數(shù)會(huì)阻塞當(dāng)前線(xiàn)程。它也會(huì)等待前面提交的任務(wù)執(zhí)行完畢在執(zhí)行該函數(shù)提交的任務(wù),等它執(zhí)行完畢后面的任務(wù)才提交到queue中執(zhí)行
dispatch_semaphore 信號(hào)量。 簡(jiǎn)單來(lái)說(shuō)就是控制訪(fǎng)問(wèn)資源的數(shù)量,比如系統(tǒng)有兩個(gè)資源可以被利用,同時(shí)有三個(gè)線(xiàn)程要訪(fǎng)問(wèn),只能允許兩個(gè)線(xiàn)程訪(fǎng)問(wèn),第三個(gè)應(yīng)當(dāng)?shù)却Y源被釋放后再訪(fǎng)問(wèn)。
下面逐一介紹與之相關(guān)的三個(gè)函數(shù):
(1)dispatch_semaphore_create(<#long value#>) 創(chuàng)建信號(hào)量,返回值類(lèi)型dispatch_semaphore_t
參數(shù)value 為允許訪(fǎng)問(wèn)資源的線(xiàn)程數(shù),該值必須 >= 0,否則會(huì)返回nil。當(dāng)value為0時(shí)對(duì)訪(fǎng)問(wèn)資源的線(xiàn)程沒(méi)有限制。
(2)dispatch_semaphore_signal(<#dispatch_semaphore_t _Nonnull dsema#>) 信號(hào)量+1
(3)dispatch_semaphore_wait(<#dispatch_semaphore_t _Nonnull dsema#>, <#dispatch_time_t timeout#> 該函數(shù)會(huì)讓信號(hào)量-1
這個(gè)函數(shù)的具體作用是這樣的,如果dsema信號(hào)量的值大于0,該函數(shù)所處線(xiàn)程就繼續(xù)執(zhí)行下面的語(yǔ)句,并且將信號(hào)量的值減1,返回值為0;如果desema的值為0,那么這個(gè)函數(shù)就阻塞當(dāng)前線(xiàn)程等待timeout,如果等待的期間desema的值被dispatch_semaphore_signal函數(shù)加1了,且該函數(shù)所處線(xiàn)程獲得了信號(hào)量,那么就繼續(xù)向下執(zhí)行并將信號(hào)量減1, 返回值為0。如果等待期間沒(méi)有獲取到信號(hào)量或者信號(hào)量的值一直為0,那么等到timeout時(shí),其所處線(xiàn)程自動(dòng)執(zhí)行其后語(yǔ)句,返回值大于0。通過(guò)返回值是否為0,可以判斷等待是否超時(shí)。
第二個(gè)函數(shù)和第一個(gè)函數(shù)是配套使用的,使用時(shí)先調(diào)用函數(shù)(3)將信號(hào)量-1,后調(diào)用函數(shù)(2)將信號(hào)量+1
為了加深理解,舉一個(gè)在餐廳排隊(duì)吃放的例子。假設(shè)某餐廳有50個(gè)座位,這相當(dāng)于調(diào)用dispatch_semaphore_wait(value: Int)參數(shù)傳入50,有顧客來(lái)吃飯相當(dāng)于函數(shù)dispatch_semaphore_wait(dsema: dispatch_semaphore_t, timeout: dispatch_time_t),前50個(gè)顧客都有座位直接就餐。第51個(gè)顧客來(lái)到時(shí)就需要等待前面的顧客吃完飯離開(kāi),他才能就坐用餐。此時(shí)相當(dāng)于dispatch_semaphore_wait函數(shù)阻塞當(dāng)前線(xiàn)程。當(dāng)有顧客用餐完畢離開(kāi)時(shí),此時(shí)就有了一個(gè)空位。相當(dāng)于dispatch_semaphore_signal將型號(hào)來(lái)加1,第51個(gè)顧客等到座位,相當(dāng)于dispatch_semaphore_wait返回值為0。
信號(hào)量是GCD同步的一種方式。前面介紹過(guò)dispatch_barrier_async是對(duì)queue中的任務(wù)進(jìn)行批量同步處理,dispatch_sync是對(duì)queue中的任務(wù)單個(gè)同步處理,而dispatch_semaphore是對(duì)queue中的某個(gè)任務(wù)中的某部分(某段代碼)同步處理。此時(shí)將dispatch_semaphore_wait中的參數(shù)傳入1。
dispatch_semaphore 的使用如下:
for (int i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
/*
其他并發(fā)操作
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
/*
同步操作,例如 [arr addObject:@(i)];
*/
[arr addObject:@(i)];
dispatch_semaphore_signal(semaphore);
/*
其他并發(fā)操作
*/
});
}
dispatch_suspend / dispatch_resume 暫停指定的隊(duì)列 / 恢復(fù)指定的隊(duì)列
有時(shí)候獲取希望提交到queue中的任務(wù)暫不執(zhí)行,等待某一時(shí)刻執(zhí)行,這時(shí)候就可使用dispatch_suspend 和 dispatch_resume, 使用dispatch_suspend時(shí)對(duì)正在執(zhí)行的任務(wù)不會(huì)起作用。
dispatch_suspend使指定隊(duì)列的暫停計(jì)數(shù)+1,dispatch_resume使指定隊(duì)列的暫停計(jì)數(shù)-1。
使用它們時(shí)要注意幾點(diǎn):
(1)dispatch_suspend 與 dispatch_resume 成對(duì)出現(xiàn) ,否則程序在運(yùn)行時(shí)會(huì)crash
(2)dispatch_suspend 與 dispatch_resume 中指定的隊(duì)列是通過(guò) dispatch_queue_create 創(chuàng)建的,其他隊(duì)列無(wú)效
dispatch_set_target_queue 前面提到過(guò),我們自己創(chuàng)建的隊(duì)列的優(yōu)先級(jí)是默認(rèn)優(yōu)先級(jí),需要更改優(yōu)先級(jí)需要調(diào)用dispatch_set_target_queue(系統(tǒng)的隊(duì)列優(yōu)先級(jí)不能修改)。實(shí)際上,dispatch_set_target_queue 主要有兩個(gè)作用:
(1)更改 dispatch_queue_create 函數(shù)創(chuàng)建的隊(duì)列的優(yōu)先級(jí)。代碼如下:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue, dispatch_get_global_queue(0, 0));
函數(shù)中第一個(gè)是需要變更優(yōu)先級(jí)的隊(duì)列,第二個(gè)參數(shù)是需要傳入全局并發(fā)隊(duì)列,并且優(yōu)先級(jí)是第一個(gè)參數(shù)想要的優(yōu)先級(jí)。
(2)修改用戶(hù)隊(duì)列的目標(biāo)隊(duì)列,使多個(gè)serial queue在目標(biāo)queue上一次只有一個(gè)執(zhí)行。第二個(gè)參數(shù)是目標(biāo)隊(duì)列,第一個(gè)參數(shù)是任務(wù)將放在目標(biāo)隊(duì)列中的另一串行隊(duì)列。
例如,用戶(hù)要依次下載小說(shuō)A、B、C,但下載任務(wù)放在不同的串行隊(duì)列中,這時(shí)就可以依次調(diào)用dispatch_set_target_queue,將放有下載任務(wù)的隊(duì)列作為第一個(gè)參數(shù)傳入,讓任務(wù)將目標(biāo)隊(duì)列中同步執(zhí)行。 示例代碼如下:
dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_set_target_queue(queue3, targetQueue);
dispatch_async(queue1, ^{
NSLog(@"開(kāi)始下載小說(shuō)A");
/*
下載中
*/
NSLog(@"小說(shuō)A下載完成");
});
dispatch_async(queue2, ^{
NSLog(@"開(kāi)始下載小說(shuō)B");
/*
下載中
*/
NSLog(@"小說(shuō)B下載完成");
});
dispatch_async(queue3, ^{
NSLog(@"開(kāi)始下載小說(shuō)C");
/*
下載中
*/
NSLog(@"小說(shuō)C下載完成");
});
