行走的少年郎 關(guān)注
2016.09.03 19:47* 字?jǐn)?shù) 10492 閱讀 41799評(píng)論 91喜歡 504
- 本文首發(fā)于我的個(gè)人博客:『不羈閣』
- 文章鏈接:傳送門
- 本文更新時(shí)間:2018-02-24 10:07:40
感謝大家對(duì)這篇文章的喜歡和支持。為了不辜負(fù)大家的喜歡,也為了更好的讓大家了解 iOS 多線程斥难,以及 GCD 的相關(guān)知識(shí),我對(duì)這篇文章進(jìn)行了重新梳理乍构,在原有文章的基礎(chǔ)上修改了原文存在的問題,并增加了更多關(guān)于 GCD 相關(guān)知識(shí)和使用方法扛点,希望大家能夠喜歡這篇新文章哥遮。
本文用來介紹 iOS 多線程中 GCD 的相關(guān)知識(shí)以及使用方法。這大概是史上最詳細(xì)陵究、清晰的關(guān)于 GCD 的詳細(xì)講解+總結(jié)的文章了眠饮。通過本文,您將了解到:
1. GCD 簡(jiǎn)介
2. GCD 任務(wù)和隊(duì)列
3. GCD 的使用步驟
4. GCD 的基本使用(6種不同組合區(qū)別)
5. GCD 線程間的通信
6. GCD 的其他方法(柵欄方法:dispatch_barrier_async铜邮、延時(shí)執(zhí)行方法:dispatch_after仪召、一次性代碼(只執(zhí)行一次):dispatch_once、快速迭代方法:dispatch_apply松蒜、隊(duì)列組:dispatch_group扔茅、信號(hào)量:dispatch_semaphore)
文中 Demo 我已放在了 Github 上,Demo 鏈接:傳送門
1. GCD 簡(jiǎn)介
什么是 GCD 呢秸苗?我們先來看看百度百科的解釋簡(jiǎn)單了解下概念
引自百度百科
Grand Central Dispatch(GCD) 是 Apple 開發(fā)的一個(gè)多核編程的較新的解決方法咖摹。它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對(duì)稱多處理系統(tǒng)。它是一個(gè)在線程池模式的基礎(chǔ)上執(zhí)行的并發(fā)任務(wù)难述。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用吐句。
為什么要用 GCD 呢胁后?
因?yàn)?GCD 有很多好處啊,具體如下:
GCD 可用于多核的并行運(yùn)算
GCD 會(huì)自動(dòng)利用更多的 CPU 內(nèi)核(比如雙核嗦枢、四核)
GCD 會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程攀芯、調(diào)度任務(wù)、銷毀線程)
程序員只需要告訴 GCD 想要執(zhí)行什么任務(wù)文虏,不需要編寫任何線程管理代碼
既然 GCD 有這么多的好處侣诺,那么下面我們就來系統(tǒng)的學(xué)習(xí)一下 GCD 的使用方法。
2. GCD 任務(wù)和隊(duì)列
學(xué)習(xí) GCD 之前氧秘,先來了解 GCD 中兩個(gè)核心概念:任務(wù)和隊(duì)列年鸳。
任務(wù):就是執(zhí)行操作的意思,換句話說就是你在線程中執(zhí)行的那段代碼丸相。在 GCD 中是放在 block 中的搔确。執(zhí)行任務(wù)有兩種方式:同步執(zhí)行(sync)和異步執(zhí)行(async)。兩者的主要區(qū)別是:是否等待隊(duì)列的任務(wù)執(zhí)行結(jié)束,以及是否具備開啟新線程的能力膳算。
同步執(zhí)行(sync):
同步添加任務(wù)到指定的隊(duì)列中座硕,在添加的任務(wù)執(zhí)行結(jié)束之前,會(huì)一直等待涕蜂,直到隊(duì)列里面的任務(wù)完成之后再繼續(xù)執(zhí)行华匾。
只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力机隙。
異步執(zhí)行(async):
異步添加任務(wù)到指定的隊(duì)列中蜘拉,它不會(huì)做任何等待,可以繼續(xù)執(zhí)行任務(wù)黍瞧。
可以在新的線程中執(zhí)行任務(wù)诸尽,具備開啟新線程的能力。
舉個(gè)簡(jiǎn)單例子:你要打電話給小明和小白印颤。
同步執(zhí)行就是您机,你打電話給小明的時(shí)候,不能同時(shí)打給小白年局,等到給小明打完了际看,才能打給小白(等待任務(wù)執(zhí)行結(jié)束)。而且只能用當(dāng)前的電話(不具備開啟新線程的能力)矢否。
而異步執(zhí)行就是仲闽,你打電話給小明的時(shí)候,不等和小明通話結(jié)束僵朗,還能直接給小白打電話赖欣,不用等著和小明通話結(jié)束再打(不用等待任務(wù)執(zhí)行結(jié)束)。除了當(dāng)前電話验庙,你還可以使用其他所能使用的電話(具備開啟新線程的能力)顶吮。
注意:異步執(zhí)行(async)雖然具有開啟新線程的能力,但是并不一定開啟新線程粪薛。這跟任務(wù)所指定的隊(duì)列類型有關(guān)(下面會(huì)講)悴了。
隊(duì)列(Dispatch Queue):這里的隊(duì)列指執(zhí)行任務(wù)的等待隊(duì)列,即用來存放任務(wù)的隊(duì)列违寿。隊(duì)列是一種特殊的線性表湃交,采用 FIFO(先進(jìn)先出)的原則,即新任務(wù)總是被插入到隊(duì)列的末尾藤巢,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開始讀取搞莺。每讀取一個(gè)任務(wù),則從隊(duì)列中釋放一個(gè)任務(wù)掂咒。隊(duì)列的結(jié)構(gòu)可參考下圖:
隊(duì)列(Dispatch Queue).png
在 GCD 中有兩種隊(duì)列:串行隊(duì)列和并發(fā)隊(duì)列腮敌。兩者都符合 FIFO(先進(jìn)先出)的原則阱当。兩者的主要區(qū)別是:執(zhí)行順序不同,以及開啟線程數(shù)不同糜工。
串行隊(duì)列(Serial Dispatch Queue):
- 每次只有一個(gè)任務(wù)被執(zhí)行弊添。讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行。(只開啟一個(gè)線程捌木,一個(gè)任務(wù)執(zhí)行完畢后油坝,再執(zhí)行下一個(gè)任務(wù))
并發(fā)隊(duì)列(Concurrent Dispatch Queue):
- 可以讓多個(gè)任務(wù)并發(fā)(同時(shí))執(zhí)行。(可以開啟多個(gè)線程刨裆,并且同時(shí)執(zhí)行任務(wù))
注意:并發(fā)隊(duì)列的并發(fā)功能只有在異步(dispatch_async)函數(shù)下才有效
兩者具體區(qū)別如下兩圖所示澈圈。
串行隊(duì)列.png
并發(fā)隊(duì)列.png
3. GCD 的使用步驟
GCD 的使用步驟其實(shí)很簡(jiǎn)單,只有兩步帆啃。
創(chuàng)建一個(gè)隊(duì)列(串行隊(duì)列或并發(fā)隊(duì)列)
將任務(wù)追加到任務(wù)的等待隊(duì)列中瞬女,然后系統(tǒng)就會(huì)根據(jù)任務(wù)類型執(zhí)行任務(wù)(同步執(zhí)行或異步執(zhí)行)
下邊來看看隊(duì)列的創(chuàng)建方法/獲取方法,以及任務(wù)的創(chuàng)建方法努潘。
3.1 隊(duì)列的創(chuàng)建方法/獲取方法
- 可以使用dispatch_queue_create來創(chuàng)建隊(duì)列诽偷,需要傳入兩個(gè)參數(shù),第一個(gè)參數(shù)表示隊(duì)列的唯一標(biāo)識(shí)符疯坤,用于 DEBUG报慕,可為空,Dispatch Queue 的名稱推薦使用應(yīng)用程序 ID 這種逆序全程域名压怠;第二個(gè)參數(shù)用來識(shí)別是串行隊(duì)列還是并發(fā)隊(duì)列眠冈。DISPATCH_QUEUE_SERIAL 表示串行隊(duì)列,DISPATCH_QUEUE_CONCURRENT 表示并發(fā)隊(duì)列菌瘫。
// 串行隊(duì)列的創(chuàng)建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 并發(fā)隊(duì)列的創(chuàng)建方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
對(duì)于串行隊(duì)列蜗顽,GCD 提供了的一種特殊的串行隊(duì)列:主隊(duì)列(Main Dispatch Queue)。
所有放在主隊(duì)列中的任務(wù)雨让,都會(huì)放到主線程中執(zhí)行诫舅。
可使用dispatch_get_main_queue()獲得主隊(duì)列。
// 主隊(duì)列的獲取方法
dispatch_queue_t queue = dispatch_get_main_queue();
對(duì)于并發(fā)隊(duì)列宫患,GCD 默認(rèn)提供了全局并發(fā)隊(duì)列(Global Dispatch Queue)。
- 可以使用dispatch_get_global_queue來獲取这弧。需要傳入兩個(gè)參數(shù)娃闲。第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí),一般用DISPATCH_QUEUE_PRIORITY_DEFAULT匾浪。第二個(gè)參數(shù)暫時(shí)沒用皇帮,用0即可。
// 全局并發(fā)隊(duì)列的獲取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3.2 任務(wù)的創(chuàng)建方法
GCD 提供了同步執(zhí)行任務(wù)的創(chuàng)建方法dispatch_sync和異步執(zhí)行任務(wù)創(chuàng)建方法dispatch_async蛋辈。
// 同步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_sync(queue, ^{
// 這里放同步執(zhí)行任務(wù)代碼
});
// 異步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_async(queue, ^{
// 這里放異步執(zhí)行任務(wù)代碼
});
雖然使用 GCD 只需兩步属拾,但是既然我們有兩種隊(duì)列(串行隊(duì)列/并發(fā)隊(duì)列)将谊,兩種任務(wù)執(zhí)行方式(同步執(zhí)行/異步執(zhí)行),那么我們就有了四種不同的組合方式渐白。這四種不同的組合方式是:
- 同步執(zhí)行 + 并發(fā)隊(duì)列
- 異步執(zhí)行 + 并發(fā)隊(duì)列
- 同步執(zhí)行 + 串行隊(duì)列
- 異步執(zhí)行 + 串行隊(duì)列
實(shí)際上尊浓,剛才還說了兩種特殊隊(duì)列:全局并發(fā)隊(duì)列、主隊(duì)列纯衍。全局并發(fā)隊(duì)列可以作為普通并發(fā)隊(duì)列來使用。但是主隊(duì)列因?yàn)橛悬c(diǎn)特殊,所以我們就又多了兩種組合方式俺祠。這樣就有六種不同的組合方式了困鸥。
- 同步執(zhí)行 + 主隊(duì)列
- 異步執(zhí)行 + 主隊(duì)列
那么這幾種不同組合方式各有什么區(qū)別呢,這里為了方便歌亲,先上結(jié)果菇用,再來講解。你可以直接查看表格結(jié)果陷揪,然后跳過 4. GCD的基本使用 惋鸥。
<colgroup><col style="width: 130px;"><col style="width: 130px;"><col style="width: 130px;"><col style="width: 130px;"></colgroup>
|
區(qū)別
|
并發(fā)隊(duì)列
|
串行隊(duì)列
|
主隊(duì)列
|
|
同步(sync)
|
沒有開啟新線程,串行執(zhí)行任務(wù)
|
沒有開啟新線程鹅龄,串行執(zhí)行任務(wù)
|
沒有開啟新線程揩慕,串行執(zhí)行任務(wù)
|
|
異步(async)
|
有開啟新線程,并發(fā)執(zhí)行任務(wù)
|
有開啟新線程(1條)扮休,串行執(zhí)行任務(wù)
|
沒有開啟新線程迎卤,串行執(zhí)行任務(wù)
|
下邊我們來分別講講這幾種不同的組合方式的使用方法。
4. GCD 的基本使用
先來講講并發(fā)隊(duì)列的兩種執(zhí)行方式玷坠。
4.1 同步執(zhí)行 + 并發(fā)隊(duì)列
- 在當(dāng)前線程中執(zhí)行任務(wù)蜗搔,不會(huì)開啟新線程,執(zhí)行完一個(gè)任務(wù)八堡,再執(zhí)行下一個(gè)任務(wù)樟凄。
/**
同步執(zhí)行 + 并發(fā)隊(duì)列
特點(diǎn):在當(dāng)前線程中執(zhí)行任務(wù),不會(huì)開啟新線程兄渺,執(zhí)行完一個(gè)任務(wù)缝龄,再執(zhí)行下一個(gè)任務(wù)。
*/
-
(void)syncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
// 追加任務(wù)1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_sync(queue, ^{
// 追加任務(wù)2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_sync(queue, ^{
// 追加任務(wù)3 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
NSLog(@"syncConcurrent---end");
}
輸出結(jié)果:
2018-02-23 20:34:55.095932+0800 YSC-GCD-demo[19892:4996930] currentThread---<NSThread: 0x60400006bbc0>{number = 1, name = main}
2018-02-23 20:34:55.096086+0800 YSC-GCD-demo[19892:4996930] syncConcurrent---begin
2018-02-23 20:34:57.097589+0800 YSC-GCD-demo[19892:4996930] 1---<NSThread: 0x60400006bbc0>{number = 1, name = main}
2018-02-23 20:34:59.099100+0800 YSC-GCD-demo[19892:4996930] 1---<NSThread: 0x60400006bbc0>{number = 1, name = main}
2018-02-23 20:35:01.099843+0800 YSC-GCD-demo[19892:4996930] 2---<NSThread: 0x60400006bbc0>{number = 1, name = main}
2018-02-23 20:35:03.101171+0800 YSC-GCD-demo[19892:4996930] 2---<NSThread: 0x60400006bbc0>{number = 1, name = main}
2018-02-23 20:35:05.101750+0800 YSC-GCD-demo[19892:4996930] 3---<NSThread: 0x60400006bbc0>{number = 1, name = main}
2018-02-23 20:35:07.102414+0800 YSC-GCD-demo[19892:4996930] 3---<NSThread: 0x60400006bbc0>{number = 1, name = main}
2018-02-23 20:35:07.102575+0800 YSC-GCD-demo[19892:4996930] syncConcurrent---end
從同步執(zhí)行 + 并發(fā)隊(duì)列中可看到:
所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的挂谍,沒有開啟新的線程(同步執(zhí)行不具備開啟新線程的能力)叔壤。
所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行的(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)。
任務(wù)按順序執(zhí)行的口叙。按順序執(zhí)行的原因:雖然并發(fā)隊(duì)列可以開啟多個(gè)線程炼绘,并且同時(shí)執(zhí)行多個(gè)任務(wù)。但是因?yàn)楸旧聿荒軇?chuàng)建新線程妄田,只有當(dāng)前線程這一個(gè)線程(同步任務(wù)不具備開啟新線程的能力)俺亮,所以也就不存在并發(fā)驮捍。而且當(dāng)前線程只有等待當(dāng)前隊(duì)列中正在執(zhí)行的任務(wù)執(zhí)行完畢之后,才能繼續(xù)接著執(zhí)行下面的操作(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)脚曾。所以任務(wù)只能一個(gè)接一個(gè)按順序執(zhí)行东且,不能同時(shí)被執(zhí)行。
4.2 異步執(zhí)行 + 并發(fā)隊(duì)列
- 可以開啟多個(gè)線程斟珊,任務(wù)交替(同時(shí))執(zhí)行苇倡。
/**
異步執(zhí)行 + 并發(fā)隊(duì)列
特點(diǎn):可以開啟多個(gè)線程,任務(wù)交替(同時(shí))執(zhí)行囤踩。
*/
-
(void)asyncConcurrent {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任務(wù)1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_async(queue, ^{
// 追加任務(wù)2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_async(queue, ^{
// 追加任務(wù)3 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
NSLog(@"asyncConcurrent---end");
}
輸出結(jié)果:
2018-02-23 20:36:41.769269+0800 YSC-GCD-demo[19929:5005237] currentThread---<NSThread: 0x604000062d80>{number = 1, name = main}
2018-02-23 20:36:41.769496+0800 YSC-GCD-demo[19929:5005237] asyncConcurrent---begin
2018-02-23 20:36:41.769725+0800 YSC-GCD-demo[19929:5005237] asyncConcurrent---end
2018-02-23 20:36:43.774442+0800 YSC-GCD-demo[19929:5005566] 2---<NSThread: 0x604000266f00>{number = 5, name = (null)}
2018-02-23 20:36:43.774440+0800 YSC-GCD-demo[19929:5005567] 3---<NSThread: 0x60000026f200>{number = 4, name = (null)}
2018-02-23 20:36:43.774440+0800 YSC-GCD-demo[19929:5005565] 1---<NSThread: 0x600000264800>{number = 3, name = (null)}
2018-02-23 20:36:45.779286+0800 YSC-GCD-demo[19929:5005567] 3---<NSThread: 0x60000026f200>{number = 4, name = (null)}
2018-02-23 20:36:45.779302+0800 YSC-GCD-demo[19929:5005565] 1---<NSThread: 0x600000264800>{number = 3, name = (null)}
2018-02-23 20:36:45.779286+0800 YSC-GCD-demo[19929:5005566] 2---<NSThread: 0x604000266f00>{number = 5, name = (null)}
在異步執(zhí)行 + 并發(fā)隊(duì)列中可以看出:
除了當(dāng)前線程(主線程)旨椒,系統(tǒng)又開啟了3個(gè)線程,并且任務(wù)是交替/同時(shí)執(zhí)行的堵漱。(異步執(zhí)行具備開啟新線程的能力综慎。且并發(fā)隊(duì)列可開啟多個(gè)線程,同時(shí)執(zhí)行多個(gè)任務(wù))勤庐。
所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才執(zhí)行的示惊。說明當(dāng)前線程沒有等待,而是直接開啟了新線程愉镰,在新線程中執(zhí)行任務(wù)(異步執(zhí)行不做等待米罚,可以繼續(xù)執(zhí)行任務(wù))。
接下來再來講講串行隊(duì)列的兩種執(zhí)行方式丈探。
4.3 同步執(zhí)行 + 串行隊(duì)列
- 不會(huì)開啟新線程录择,在當(dāng)前線程執(zhí)行任務(wù)。任務(wù)是串行的碗降,執(zhí)行完一個(gè)任務(wù)隘竭,再執(zhí)行下一個(gè)任務(wù)。
/**
同步執(zhí)行 + 串行隊(duì)列
特點(diǎn):不會(huì)開啟新線程讼渊,在當(dāng)前線程執(zhí)行任務(wù)动看。任務(wù)是串行的,執(zhí)行完一個(gè)任務(wù)爪幻,再執(zhí)行下一個(gè)任務(wù)菱皆。
*/
-
(void)syncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任務(wù)1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_sync(queue, ^{
// 追加任務(wù)2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_sync(queue, ^{
// 追加任務(wù)3 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
NSLog(@"syncSerial---end");
}
輸出結(jié)果為:
2018-02-23 20:39:37.876811+0800 YSC-GCD-demo[19975:5017162] currentThread---<NSThread: 0x604000079400>{number = 1, name = main}
2018-02-23 20:39:37.876998+0800 YSC-GCD-demo[19975:5017162] syncSerial---begin
2018-02-23 20:39:39.878316+0800 YSC-GCD-demo[19975:5017162] 1---<NSThread: 0x604000079400>{number = 1, name = main}
2018-02-23 20:39:41.879829+0800 YSC-GCD-demo[19975:5017162] 1---<NSThread: 0x604000079400>{number = 1, name = main}
2018-02-23 20:39:43.880660+0800 YSC-GCD-demo[19975:5017162] 2---<NSThread: 0x604000079400>{number = 1, name = main}
2018-02-23 20:39:45.881265+0800 YSC-GCD-demo[19975:5017162] 2---<NSThread: 0x604000079400>{number = 1, name = main}
2018-02-23 20:39:47.882257+0800 YSC-GCD-demo[19975:5017162] 3---<NSThread: 0x604000079400>{number = 1, name = main}
2018-02-23 20:39:49.883008+0800 YSC-GCD-demo[19975:5017162] 3---<NSThread: 0x604000079400>{number = 1, name = main}
2018-02-23 20:39:49.883253+0800 YSC-GCD-demo[19975:5017162] syncSerial---end
在同步執(zhí)行 + 串行隊(duì)列可以看到:
所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的,并沒有開啟新的線程(同步執(zhí)行不具備開啟新線程的能力)挨稿。
所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)仇轻。
任務(wù)是按順序執(zhí)行的(串行隊(duì)列每次只有一個(gè)任務(wù)被執(zhí)行,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)叶组。
4.4 異步執(zhí)行 + 串行隊(duì)列
- 會(huì)開啟新線程,但是因?yàn)槿蝿?wù)是串行的历造,執(zhí)行完一個(gè)任務(wù)甩十,再執(zhí)行下一個(gè)任務(wù)
/**
異步執(zhí)行 + 串行隊(duì)列
特點(diǎn):會(huì)開啟新線程船庇,但是因?yàn)槿蝿?wù)是串行的,執(zhí)行完一個(gè)任務(wù)侣监,再執(zhí)行下一個(gè)任務(wù)鸭轮。
*/
-
(void)asyncSerial {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任務(wù)1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_async(queue, ^{
// 追加任務(wù)2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_async(queue, ^{
// 追加任務(wù)3 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
NSLog(@"asyncSerial---end");
}
輸出結(jié)果為:
2018-02-23 20:41:17.029999+0800 YSC-GCD-demo[20008:5024757] currentThread---<NSThread: 0x604000070440>{number = 1, name = main}
2018-02-23 20:41:17.030212+0800 YSC-GCD-demo[20008:5024757] asyncSerial---begin
2018-02-23 20:41:17.030364+0800 YSC-GCD-demo[20008:5024757] asyncSerial---end
2018-02-23 20:41:19.035379+0800 YSC-GCD-demo[20008:5024950] 1---<NSThread: 0x60000026e100>{number = 3, name = (null)}
2018-02-23 20:41:21.037140+0800 YSC-GCD-demo[20008:5024950] 1---<NSThread: 0x60000026e100>{number = 3, name = (null)}
2018-02-23 20:41:23.042220+0800 YSC-GCD-demo[20008:5024950] 2---<NSThread: 0x60000026e100>{number = 3, name = (null)}
2018-02-23 20:41:25.042971+0800 YSC-GCD-demo[20008:5024950] 2---<NSThread: 0x60000026e100>{number = 3, name = (null)}
2018-02-23 20:41:27.047690+0800 YSC-GCD-demo[20008:5024950] 3---<NSThread: 0x60000026e100>{number = 3, name = (null)}
2018-02-23 20:41:29.052327+0800 YSC-GCD-demo[20008:5024950] 3---<NSThread: 0x60000026e100>{number = 3, name = (null)}
在異步執(zhí)行 + 串行隊(duì)列可以看到:
開啟了一條新線程(異步執(zhí)行具備開啟新線程的能力,串行隊(duì)列只開啟一個(gè)線程)橄霉。
所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的(異步執(zhí)行不會(huì)做任何等待窃爷,可以繼續(xù)執(zhí)行任務(wù))。
任務(wù)是按順序執(zhí)行的(串行隊(duì)列每次只有一個(gè)任務(wù)被執(zhí)行姓蜂,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)按厘。
下邊講講剛才我們提到過的特殊隊(duì)列:主隊(duì)列。
主隊(duì)列:GCD自帶的一種特殊的串行隊(duì)列
所有放在主隊(duì)列中的任務(wù)钱慢,都會(huì)放到主線程中執(zhí)行
可使用dispatch_get_main_queue()獲得主隊(duì)列
我們?cè)賮砜纯粗麝?duì)列的兩種組合方式逮京。
4.5 同步執(zhí)行 + 主隊(duì)列
同步執(zhí)行 + 主隊(duì)列在不同線程中調(diào)用結(jié)果也是不一樣,在主線程中調(diào)用會(huì)出現(xiàn)死鎖束莫,而在其他線程中則不會(huì)懒棉。
4.5.1 在主線程中調(diào)用同步執(zhí)行 + 主隊(duì)列
- 互相等待卡住不可行
/**
同步執(zhí)行 + 主隊(duì)列
特點(diǎn)(主線程調(diào)用):互等卡主不執(zhí)行。
特點(diǎn)(其他線程調(diào)用):不會(huì)開啟新線程览绿,執(zhí)行完一個(gè)任務(wù)策严,再執(zhí)行下一個(gè)任務(wù)。
*/
-
(void)syncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 追加任務(wù)1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_sync(queue, ^{
// 追加任務(wù)2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_sync(queue, ^{
// 追加任務(wù)3 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
NSLog(@"syncMain---end");
}
輸出結(jié)果
2018-02-23 20:42:36.842892+0800 YSC-GCD-demo[20041:5030982] currentThread---<NSThread: 0x600000078a00>{number = 1, name = main}
2018-02-23 20:42:36.843050+0800 YSC-GCD-demo[20041:5030982] syncMain---begin
(lldb)
在同步執(zhí)行 + 主隊(duì)列可以驚奇的發(fā)現(xiàn):
- 在主線程中使用同步執(zhí)行 + 主隊(duì)列饿敲,追加到主線程的任務(wù)1妻导、任務(wù)2、任務(wù)3都不再執(zhí)行了诀蓉,而且syncMain---end也沒有打印栗竖,在XCode 9上還會(huì)報(bào)崩潰。這是為什么呢渠啤?
這是因?yàn)槲覀冊(cè)谥骶€程中執(zhí)行syncMain方法狐肢,相當(dāng)于把syncMain任務(wù)放到了主線程的隊(duì)列中。而同步執(zhí)行會(huì)等待當(dāng)前隊(duì)列中的任務(wù)執(zhí)行完畢沥曹,才會(huì)接著執(zhí)行份名。那么當(dāng)我們把任務(wù)1追加到主隊(duì)列中,任務(wù)1就在等待主線程處理完syncMain任務(wù)妓美。而syncMain任務(wù)需要等待任務(wù)1執(zhí)行完畢僵腺,才能接著執(zhí)行。
那么壶栋,現(xiàn)在的情況就是syncMain任務(wù)和任務(wù)1都在等對(duì)方執(zhí)行完畢辰如。這樣大家互相等待,所以就卡住了贵试,所以我們的任務(wù)執(zhí)行不了琉兜,而且syncMain---end也沒有打印凯正。
要是如果不在主線程中調(diào)用,而在其他線程中調(diào)用會(huì)如何呢豌蟋?
4.5.2 在其他線程中調(diào)用同步執(zhí)行 + 主隊(duì)列
- 不會(huì)開啟新線程廊散,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)
// 使用 NSThread 的 detachNewThreadSelector 方法會(huì)創(chuàng)建線程梧疲,并自動(dòng)啟動(dòng)線程執(zhí)行
selector 任務(wù)
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
輸出結(jié)果:
2018-02-23 20:44:19.377321+0800 YSC-GCD-demo[20083:5040347] currentThread---<NSThread: 0x600000272fc0>{number = 3, name = (null)}
2018-02-23 20:44:19.377494+0800 YSC-GCD-demo[20083:5040347] syncMain---begin
2018-02-23 20:44:21.384716+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}
2018-02-23 20:44:23.386091+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}
2018-02-23 20:44:25.387687+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}
2018-02-23 20:44:27.388648+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}
2018-02-23 20:44:29.390459+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}
2018-02-23 20:44:31.391965+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}
2018-02-23 20:44:31.392513+0800 YSC-GCD-demo[20083:5040347] syncMain---end
在其他線程中使用同步執(zhí)行 + 主隊(duì)列可看到:
所有任務(wù)都是在主線程(非當(dāng)前線程)中執(zhí)行的允睹,沒有開啟新的線程(所有放在主隊(duì)列中的任務(wù),都會(huì)放到主線程中執(zhí)行)幌氮。
所有任務(wù)都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行(同步任務(wù)需要等待隊(duì)列的任務(wù)執(zhí)行結(jié)束)缭受。
任務(wù)是按順序執(zhí)行的(主隊(duì)列是串行隊(duì)列,每次只有一個(gè)任務(wù)被執(zhí)行浩销,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)贯涎。
為什么現(xiàn)在就不會(huì)卡住了呢?
因?yàn)閟yncMain 任務(wù)放到了其他線程里慢洋,而任務(wù)1塘雳、任務(wù)2、任務(wù)3都在追加到主隊(duì)列中普筹,這三個(gè)任務(wù)都會(huì)在主線程中執(zhí)行败明。syncMain 任務(wù)在其他線程中執(zhí)行到追加任務(wù)1到主隊(duì)列中,因?yàn)橹麝?duì)列現(xiàn)在沒有正在執(zhí)行的任務(wù)太防,所以妻顶,會(huì)直接執(zhí)行主隊(duì)列的任務(wù)1,等任務(wù)1執(zhí)行完畢蜒车,再接著執(zhí)行任務(wù)2讳嘱、任務(wù)3。所以這里不會(huì)卡住線程酿愧。
4.6 異步執(zhí)行 + 主隊(duì)列
- 只在主線程中執(zhí)行任務(wù)沥潭,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)嬉挡。
/**
異步執(zhí)行 + 主隊(duì)列
特點(diǎn):只在主線程中執(zhí)行任務(wù)钝鸽,執(zhí)行完一個(gè)任務(wù),再執(zhí)行下一個(gè)任務(wù)
*/
-
(void)asyncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 追加任務(wù)1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_async(queue, ^{
// 追加任務(wù)2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_async(queue, ^{
// 追加任務(wù)3 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
NSLog(@"asyncMain---end");
}
輸出結(jié)果:
2018-02-23 20:45:49.981505+0800 YSC-GCD-demo[20111:5046708] currentThread---<NSThread: 0x60000006d440>{number = 1, name = main}
2018-02-23 20:45:49.981935+0800 YSC-GCD-demo[20111:5046708] asyncMain---begin
2018-02-23 20:45:49.982352+0800 YSC-GCD-demo[20111:5046708] asyncMain---end
2018-02-23 20:45:51.991096+0800 YSC-GCD-demo[20111:5046708] 1---<NSThread: 0x60000006d440>{number = 1, name = main}
2018-02-23 20:45:53.991959+0800 YSC-GCD-demo[20111:5046708] 1---<NSThread: 0x60000006d440>{number = 1, name = main}
2018-02-23 20:45:55.992937+0800 YSC-GCD-demo[20111:5046708] 2---<NSThread: 0x60000006d440>{number = 1, name = main}
2018-02-23 20:45:57.993649+0800 YSC-GCD-demo[20111:5046708] 2---<NSThread: 0x60000006d440>{number = 1, name = main}
2018-02-23 20:45:59.994928+0800 YSC-GCD-demo[20111:5046708] 3---<NSThread: 0x60000006d440>{number = 1, name = main}
2018-02-23 20:46:01.995589+0800 YSC-GCD-demo[20111:5046708] 3---<NSThread: 0x60000006d440>{number = 1, name = main}
在異步執(zhí)行 + 主隊(duì)列可以看到:
所有任務(wù)都是在當(dāng)前線程(主線程)中執(zhí)行的庞钢,并沒有開啟新的線程(雖然異步執(zhí)行具備開啟線程的能力拔恰,但因?yàn)槭侵麝?duì)列,所以所有任務(wù)都在主線程中)基括。
所有任務(wù)是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的(異步執(zhí)行不會(huì)做任何等待颜懊,可以繼續(xù)執(zhí)行任務(wù))。
任務(wù)是按順序執(zhí)行的(因?yàn)橹麝?duì)列是串行隊(duì)列,每次只有一個(gè)任務(wù)被執(zhí)行河爹,任務(wù)一個(gè)接一個(gè)按順序執(zhí)行)使鹅。
弄懂了難理解、繞來繞去的隊(duì)列+任務(wù)之后昌抠,我們來學(xué)習(xí)一個(gè)簡(jiǎn)單的東西:5. GCD 線程間的通信。
5. GCD 線程間的通信
在iOS開發(fā)過程中鲁僚,我們一般在主線程里邊進(jìn)行UI刷新炊苫,例如:點(diǎn)擊、滾動(dòng)冰沙、拖拽等事件侨艾。我們通常把一些耗時(shí)的操作放在其他線程,比如說圖片下載拓挥、文件上傳等耗時(shí)操作唠梨。而當(dāng)我們有時(shí)候在其他線程完成了耗時(shí)操作時(shí),需要回到主線程侥啤,那么就用到了線程之間的通訊当叭。
/**
- 線程間通信
*/
-
(void)communication {
// 獲取全局并發(fā)隊(duì)列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 獲取主隊(duì)列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 異步追加任務(wù) for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 } // 回到主線程 dispatch_async(mainQueue, ^{ // 追加在主線程中執(zhí)行的任務(wù) [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 });
});
}
輸出結(jié)果:
2018-02-23 20:47:03.462394+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
2018-02-23 20:47:05.465912+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
2018-02-23 20:47:07.466657+0800 YSC-GCD-demo[20154:5052953] 2---<NSThread: 0x60000007bf80>{number = 1, name = main}
- 可以看到在其他線程中先執(zhí)行任務(wù),執(zhí)行完了之后回到主線程執(zhí)行主線程的相應(yīng)操作盖灸。
6. GCD 的其他方法
6.1 GCD 柵欄方法:dispatch_barrier_async
-
我們有時(shí)需要異步執(zhí)行兩組操作蚁鳖,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作赁炎。這樣我們就需要一個(gè)相當(dāng)于柵欄一樣的一個(gè)方法將兩組異步執(zhí)行的操作組給分割起來醉箕,當(dāng)然這里的操作組里可以包含一個(gè)或多個(gè)任務(wù)。這就需要用到dispatch_barrier_async方法在兩個(gè)操作組間形成柵欄徙垫。
dispatch_barrier_async函數(shù)會(huì)等待前邊追加到并發(fā)隊(duì)列中的任務(wù)全部執(zhí)行完畢之后讥裤,再將指定的任務(wù)追加到該異步隊(duì)列中。然后在dispatch_barrier_async函數(shù)追加的任務(wù)執(zhí)行完畢之后姻报,異步隊(duì)列才恢復(fù)為一般動(dòng)作己英,接著追加任務(wù)到該異步隊(duì)列并開始執(zhí)行。具體如下圖所示:
dispatch_barrier_async.png
/**
- 柵欄方法 dispatch_barrier_async
*/
-
(void)barrier {
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任務(wù)1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_async(queue, ^{
// 追加任務(wù)2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_barrier_async(queue, ^{
// 追加任務(wù) barrier for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"barrier---%@",[NSThread currentThread]);// 打印當(dāng)前線程 }
});
dispatch_async(queue, ^{
// 追加任務(wù)3 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_async(queue, ^{
// 追加任務(wù)4 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"4---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
}
輸出結(jié)果:
2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
2018-02-23 20:48:22.306290+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
2018-02-23 20:48:24.311655+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
2018-02-23 20:48:26.316943+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
2018-02-23 20:48:26.316956+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}
2018-02-23 20:48:28.320660+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
2018-02-23 20:48:28.320649+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}
在dispatch_barrier_async執(zhí)行結(jié)果中可以看出:
- 在執(zhí)行完?yáng)艡谇懊娴牟僮髦蠖阂郑艌?zhí)行柵欄操作剧辐,最后再執(zhí)行柵欄后邊的操作。
6.2 GCD 延時(shí)執(zhí)行方法:dispatch_after
我們經(jīng)常會(huì)遇到這樣的需求:在指定時(shí)間(例如3秒)之后執(zhí)行某個(gè)任務(wù)邮府∮兀可以用 GCD 的dispatch_after函數(shù)來實(shí)現(xiàn)。
需要注意的是:dispatch_after函數(shù)并不是在指定時(shí)間之后才開始執(zhí)行處理褂傀,而是在指定時(shí)間之后將任務(wù)追加到主隊(duì)列中忍啤。嚴(yán)格來說,這個(gè)時(shí)間并不是絕對(duì)準(zhǔn)確的,但想要大致延遲執(zhí)行任務(wù)同波,dispatch_after函數(shù)是很有效的鳄梅。
/**
- 延時(shí)執(zhí)行方法 dispatch_after
*/
-
(void)after {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"asyncMain---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒后異步追加任務(wù)代碼到主隊(duì)列,并開始執(zhí)行 NSLog(@"after---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
}
輸出結(jié)果:
2018-02-23 20:53:08.713784+0800 YSC-GCD-demo[20282:5080295] currentThread---<NSThread: 0x60000006ee00>{number = 1, name = main}
2018-02-23 20:53:08.713962+0800 YSC-GCD-demo[20282:5080295] asyncMain---begin
2018-02-23 20:53:10.714283+0800 YSC-GCD-demo[20282:5080295] after---<NSThread: 0x60000006ee00>{number = 1, name = main}
可以看出:在打印 asyncMain---begin 之后大約 2.0 秒的時(shí)間未檩,打印了 after---<NSThread: 0x60000006ee00>{number = 1, name = main}
6.3 GCD 一次性代碼(只執(zhí)行一次):dispatch_once
-
我們?cè)趧?chuàng)建單例戴尸、或者有整個(gè)程序運(yùn)行過程中只執(zhí)行一次的代碼時(shí),我們就用到了 GCD 的 dispatch_once 函數(shù)冤狡。使用
dispatch_once 函數(shù)能保證某段代碼在程序運(yùn)行過程中只被執(zhí)行1次孙蒙,并且即使在多線程的環(huán)境下,dispatch_once也可以保證線程安全悲雳。
/**
- 一次性代碼(只執(zhí)行一次)dispatch_once
*/
-
(void)once {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認(rèn)是線程安全的)
});
}
6.4 GCD 快速迭代方法:dispatch_apply
- 通常我們會(huì)用 for 循環(huán)遍歷挎峦,但是 GCD 給我們提供了快速迭代的函數(shù)dispatch_apply。dispatch_apply按照指定的次數(shù)將指定的任務(wù)追加到指定的隊(duì)列中合瓢,并等待全部隊(duì)列執(zhí)行結(jié)束坦胶。
我們可以利用異步隊(duì)列同時(shí)遍歷。比如說遍歷 0~5 這6個(gè)數(shù)字晴楔,for 循環(huán)的做法是每次取出一個(gè)元素顿苇,逐個(gè)遍歷。dispatch_apply可以同時(shí)遍歷多個(gè)數(shù)字税弃。
/**
- 快速迭代方法 dispatch_apply
*/
-
(void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
}
輸出結(jié)果:
2018-02-23 22:03:18.475499+0800 YSC-GCD-demo[20470:5176805] apply---begin
2018-02-23 22:03:18.476672+0800 YSC-GCD-demo[20470:5177035] 1---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
2018-02-23 22:03:18.476693+0800 YSC-GCD-demo[20470:5176805] 0---<NSThread: 0x604000070640>{number = 1, name = main}
2018-02-23 22:03:18.476704+0800 YSC-GCD-demo[20470:5177037] 2---<NSThread: 0x604000276800>{number = 4, name = (null)}
2018-02-23 22:03:18.476735+0800 YSC-GCD-demo[20470:5177036] 3---<NSThread: 0x60000027b800>{number = 5, name = (null)}
2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5177035] 4---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5176805] 5---<NSThread: 0x604000070640>{number = 1, name = main}
2018-02-23 22:03:18.477038+0800 YSC-GCD-demo[20470:5176805] apply---end
因?yàn)槭窃诓l(fā)隊(duì)列中異步隊(duì)執(zhí)行任務(wù)岖圈,所以各個(gè)任務(wù)的執(zhí)行時(shí)間長(zhǎng)短不定,最后結(jié)束順序也不定钙皮。但是apply---end一定在最后執(zhí)行蜂科。這是因?yàn)閐ispatch_apply函數(shù)會(huì)等待全部任務(wù)執(zhí)行完畢。
6.5 GCD 隊(duì)列組:dispatch_group
有時(shí)候我們會(huì)有這樣的需求:分別異步執(zhí)行2個(gè)耗時(shí)任務(wù)短条,然后當(dāng)2個(gè)耗時(shí)任務(wù)都執(zhí)行完畢后再回到主線程執(zhí)行任務(wù)导匣。這時(shí)候我們可以用到 GCD 的隊(duì)列組。
-
調(diào)用隊(duì)列組的 dispatch_group_async 先把任務(wù)放到隊(duì)列中茸时,然后將隊(duì)列放入隊(duì)列組中贡定。或者使用隊(duì)列組的 dispatch_group_enter可都、dispatch_group_leave 組合 來實(shí)現(xiàn)
dispatch_group_async缓待。
調(diào)用隊(duì)列組的 dispatch_group_notify 回到指定線程執(zhí)行任務(wù)∏或者使用 dispatch_group_wait 回到當(dāng)前線程繼續(xù)向下執(zhí)行(會(huì)阻塞當(dāng)前線程)旋炒。
6.5.1 dispatch_group_notify
- 監(jiān)聽 group 中任務(wù)的完成狀態(tài),當(dāng)所有的任務(wù)都執(zhí)行完成后签杈,追加任務(wù)到 group 中瘫镇,并執(zhí)行任務(wù)。
/**
- 隊(duì)列組 dispatch_group_notify
*/
-
(void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務(wù)1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務(wù)2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步任務(wù)1、任務(wù)2都執(zhí)行完畢后铣除,回到主線程執(zhí)行下邊任務(wù) for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程 } NSLog(@"group---end");
});
}
輸出結(jié)果:
2018-02-23 22:05:03.790035+0800 YSC-GCD-demo[20494:5183349] currentThread---<NSThread: 0x604000072040>{number = 1, name = main}
2018-02-23 22:05:03.790237+0800 YSC-GCD-demo[20494:5183349] group---begin
2018-02-23 22:05:05.792721+0800 YSC-GCD-demo[20494:5183654] 1---<NSThread: 0x60000026f280>{number = 4, name = (null)}
2018-02-23 22:05:05.792725+0800 YSC-GCD-demo[20494:5183656] 2---<NSThread: 0x60000026f240>{number = 3, name = (null)}
2018-02-23 22:05:07.797408+0800 YSC-GCD-demo[20494:5183656] 2---<NSThread: 0x60000026f240>{number = 3, name = (null)}
2018-02-23 22:05:07.797408+0800 YSC-GCD-demo[20494:5183654] 1---<NSThread: 0x60000026f280>{number = 4, name = (null)}
2018-02-23 22:05:09.798717+0800 YSC-GCD-demo[20494:5183349] 3---<NSThread: 0x604000072040>{number = 1, name = main}
2018-02-23 22:05:11.799827+0800 YSC-GCD-demo[20494:5183349] 3---<NSThread: 0x604000072040>{number = 1, name = main}
2018-02-23 22:05:11.799977+0800 YSC-GCD-demo[20494:5183349] group---end
從dispatch_group_notify相關(guān)代碼運(yùn)行輸出結(jié)果可以看出:
當(dāng)所有任務(wù)都執(zhí)行完成之后谚咬,才執(zhí)行dispatch_group_notify block 中的任務(wù)。
6.5.2 dispatch_group_wait
- 暫停當(dāng)前線程(阻塞當(dāng)前線程)尚粘,等待指定的 group 中的任務(wù)執(zhí)行完成后择卦,才會(huì)往下繼續(xù)執(zhí)行。
/**
- 隊(duì)列組 dispatch_group_wait
*/
-
(void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務(wù)1 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務(wù)2 for (int i = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程 }
});
// 等待上面的任務(wù)全部完成后郎嫁,會(huì)往下繼續(xù)執(zhí)行(會(huì)阻塞當(dāng)前線程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
輸出結(jié)果:
2018-02-23 22:10:16.939258+0800 YSC-GCD-demo[20538:5198871] currentThread---<NSThread: 0x600000066780>{number = 1, name = main}
2018-02-23 22:10:16.939455+0800 YSC-GCD-demo[20538:5198871] group---begin
2018-02-23 22:10:18.943862+0800 YSC-GCD-demo[20538:5199137] 2---<NSThread: 0x600000464b80>{number = 4, name = (null)}
2018-02-23 22:10:18.943861+0800 YSC-GCD-demo[20538:5199138] 1---<NSThread: 0x604000076640>{number = 3, name = (null)}
2018-02-23 22:10:20.947787+0800 YSC-GCD-demo[20538:5199137] 2---<NSThread: 0x600000464b80>{number = 4, name = (null)}
2018-02-23 22:10:20.947790+0800 YSC-GCD-demo[20538:5199138] 1---<NSThread: 0x604000076640>{number = 3, name = (null)}
2018-02-23 22:10:20.948134+0800 YSC-GCD-demo[20538:5198871] group---end
從dispatch_group_wait相關(guān)代碼運(yùn)行輸出結(jié)果可以看出:
當(dāng)所有任務(wù)執(zhí)行完成之后互捌,才執(zhí)行 dispatch_group_wait 之后的操作。但是行剂,使用dispatch_group_wait 會(huì)阻塞當(dāng)前線程。
6.5.3 dispatch_group_enter钳降、dispatch_group_leave
dispatch_group_enter 標(biāo)志著一個(gè)任務(wù)追加到 group厚宰,執(zhí)行一次,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù)+1
dispatch_group_leave 標(biāo)志著一個(gè)任務(wù)離開了 group遂填,執(zhí)行一次铲觉,相當(dāng)于 group 中未執(zhí)行完畢任務(wù)數(shù)-1。
當(dāng) group 中未執(zhí)行完畢任務(wù)數(shù)為0的時(shí)候吓坚,才會(huì)使dispatch_group_wait解除阻塞撵幽,以及執(zhí)行追加到dispatch_group_notify中的任務(wù)。
/**
- 隊(duì)列組 dispatch_group_enter礁击、dispatch_group_leave
*/
- (void)groupEnterAndLeave
{
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任務(wù)1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任務(wù)2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執(zhí)行完畢后盐杂,回到主線程.
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
}
NSLog(@"group---end");
});
// // 等待上面的任務(wù)全部完成后,會(huì)往下繼續(xù)執(zhí)行(會(huì)阻塞當(dāng)前線程)
// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//
// NSLog(@"group---end");
}
輸出結(jié)果:
2018-02-23 22:14:17.997667+0800 YSC-GCD-demo[20592:5214830] currentThread---<NSThread: 0x604000066600>{number = 1, name = main}
2018-02-23 22:14:17.997839+0800 YSC-GCD-demo[20592:5214830] group---begin
2018-02-23 22:14:20.000298+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}
2018-02-23 22:14:20.000305+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}
2018-02-23 22:14:22.001323+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}
2018-02-23 22:14:22.001339+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}
2018-02-23 22:14:24.002321+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}
2018-02-23 22:14:26.002852+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}
2018-02-23 22:14:26.003116+0800 YSC-GCD-demo[20592:5214830] group---end
從dispatch_group_enter哆窿、dispatch_group_leave相關(guān)代碼運(yùn)行結(jié)果中可以看出:當(dāng)所有任務(wù)執(zhí)行完成之后链烈,才執(zhí)行 dispatch_group_notify 中的任務(wù)。這里的dispatch_group_enter挚躯、dispatch_group_leave組合强衡,其實(shí)等同于dispatch_group_async。
6.6 GCD 信號(hào)量:dispatch_semaphore
GCD 中的信號(hào)量是指 Dispatch Semaphore码荔,是持有計(jì)數(shù)的信號(hào)漩勤。類似于過高速路收費(fèi)站的欄桿∷踅粒可以通過時(shí)越败,打開欄桿,不可以通過時(shí)硼瓣,關(guān)閉欄桿眉尸。在 Dispatch Semaphore 中,使用計(jì)數(shù)來完成這個(gè)功能,計(jì)數(shù)為0時(shí)等待噪猾,不可通過霉祸。計(jì)數(shù)為1或大于1時(shí),計(jì)數(shù)減1且不等待袱蜡,可通過丝蹭。
Dispatch Semaphore 提供了三個(gè)函數(shù)。
dispatch_semaphore_create:創(chuàng)建一個(gè)Semaphore并初始化信號(hào)的總量
dispatch_semaphore_signal:發(fā)送一個(gè)信號(hào)坪蚁,讓信號(hào)總量加1
dispatch_semaphore_wait:可以使總信號(hào)量減1奔穿,當(dāng)信號(hào)總量為0時(shí)就會(huì)一直等待(阻塞所在線程),否則就可以正常執(zhí)行敏晤。
注意:信號(hào)量的使用前提是:想清楚你需要處理哪個(gè)線程等待(阻塞)贱田,又要哪個(gè)線程繼續(xù)執(zhí)行,然后使用信號(hào)量嘴脾。
Dispatch Semaphore 在實(shí)際開發(fā)中主要用于:
保持線程同步男摧,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)
保證線程安全,為線程加鎖
6.6.1 Dispatch Semaphore 線程同步
我們?cè)陂_發(fā)中译打,會(huì)遇到這樣的需求:異步執(zhí)行耗時(shí)任務(wù)耗拓,并使用異步執(zhí)行的結(jié)果進(jìn)行一些額外的操作。換句話說奏司,相當(dāng)于乔询,將將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)。比如說:AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法韵洋。通過引入信號(hào)量的方式竿刁,等待異步執(zhí)行任務(wù)結(jié)果,獲取到 tasks搪缨,然后再返回該 tasks们妥。
-
(NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) { tasks = dataTasks; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) { tasks = uploadTasks; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) { tasks = downloadTasks; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) { tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"]; } dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
下面,我們來利用 Dispatch Semaphore 實(shí)現(xiàn)線程同步勉吻,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)监婶。
/**
- semaphore 線程同步
*/
-
(void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任務(wù)1 [NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程 number = 100; dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}
輸出結(jié)果:
2018-02-23 22:22:26.521665+0800 YSC-GCD-demo[20642:5246341] currentThread---<NSThread: 0x60400006bc80>{number = 1, name = main}
2018-02-23 22:22:26.521869+0800 YSC-GCD-demo[20642:5246341] semaphore---begin
2018-02-23 22:22:28.526841+0800 YSC-GCD-demo[20642:5246638] 1---<NSThread: 0x600000272300>{number = 3, name = (null)}
2018-02-23 22:22:28.527030+0800 YSC-GCD-demo[20642:5246341] semaphore---end,number = 100
從 Dispatch Semaphore 實(shí)現(xiàn)線程同步的代碼可以看到:
-
semaphore---end 是在執(zhí)行完 number = 100; 之后才打印的。而且輸出結(jié)果 number 為 100齿桃。
這是因?yàn)楫惒綀?zhí)行不會(huì)做任何等待惑惶,可以繼續(xù)執(zhí)行任務(wù)。異步執(zhí)行將任務(wù)1追加到隊(duì)列之后短纵,不做等待带污,接著執(zhí)行dispatch_semaphore_wait方法。此時(shí) semaphore == 0香到,當(dāng)前線程進(jìn)入等待狀態(tài)鱼冀。然后报破,異步任務(wù)1開始執(zhí)行。任務(wù)1執(zhí)行到dispatch_semaphore_signal之后千绪,總信號(hào)量充易,此時(shí) semaphore == 1,dispatch_semaphore_wait方法使總信號(hào)量減1荸型,正在被阻塞的線程(主線程)恢復(fù)繼續(xù)執(zhí)行盹靴。最后打印semaphore---end,number = 100。這樣就實(shí)現(xiàn)了線程同步瑞妇,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)稿静。
6.6.2 Dispatch Semaphore 線程安全和線程同步(為線程加鎖)
線程安全:如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼辕狰。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的改备,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的蔓倍。
若每個(gè)線程中對(duì)全局變量悬钳、靜態(tài)變量只有讀操作,而無寫操作柬脸,一般來說,這個(gè)全局變量是線程安全的毙驯;若有多個(gè)線程同時(shí)執(zhí)行寫操作(更改變量)倒堕,一般都需要考慮線程同步,否則的話就可能影響線程安全爆价。
線程同步:可理解為線程 A 和 線程 B 一塊配合垦巴,A 執(zhí)行到一定程度時(shí)要依靠線程 B 的某個(gè)結(jié)果,于是停下來铭段,示意 B 運(yùn)行骤宣;B 依言執(zhí)行,再將結(jié)果給 A序愚;A 再繼續(xù)操作憔披。
舉個(gè)簡(jiǎn)單例子就是:兩個(gè)人在一起聊天。兩個(gè)人不能同時(shí)說話爸吮,避免聽不清(操作沖突)芬膝。等一個(gè)人說完(一個(gè)線程結(jié)束操作),另一個(gè)再說(另一個(gè)線程再開始操作)形娇。
下面锰霜,我們模擬火車票售賣的方式,實(shí)現(xiàn) NSThread 線程安全和解決線程同步問題桐早。
場(chǎng)景:總共有50張火車票癣缅,有兩個(gè)售賣火車票的窗口厨剪,一個(gè)是北京火車票售賣窗口,另一個(gè)是上河汛妫火車票售賣窗口祷膳。兩個(gè)窗口同時(shí)售賣火車票,賣完為止爬立。
6.6.2.1 非線程安全(不使用 semaphore)
先來看看不考慮線程安全的代碼:
/**
非線程安全:不使用 semaphore
初始化火車票數(shù)量钾唬、賣票窗口(非線程安全)、并開始賣票
*/
-
(void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"semaphore---begin");
self.ticketSurplusCount = 50;
// queue1 代表北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上合姥保火車票售賣窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
- 售賣火車票(非線程安全)
*/
-
(void)saleTicketNotSafe {
while (1) {
if (self.ticketSurplusCount > 0) { //如果還有票抡秆,繼續(xù)售賣 self.ticketSurplusCount--; NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]); [NSThread sleepForTimeInterval:0.2]; } else { //如果已賣完,關(guān)閉售票窗口 NSLog(@"所有火車票均已售完"); break; }
}
}
輸出結(jié)果(部分):
2018-02-23 22:25:35.789072+0800 YSC-GCD-demo[20712:5258914] currentThread---<NSThread: 0x604000068880>{number = 1, name = main}
2018-02-23 22:25:35.789260+0800 YSC-GCD-demo[20712:5258914] semaphore---begin
2018-02-23 22:25:35.789641+0800 YSC-GCD-demo[20712:5259176] 剩余票數(shù):48 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}
2018-02-23 22:25:35.789646+0800 YSC-GCD-demo[20712:5259175] 剩余票數(shù):49 窗口:<NSThread: 0x60000027e740>{number = 4, name = (null)}
2018-02-23 22:25:35.994113+0800 YSC-GCD-demo[20712:5259175] 剩余票數(shù):47 窗口:<NSThread: 0x60000027e740>{number = 4, name = (null)}
2018-02-23 22:25:35.994129+0800 YSC-GCD-demo[20712:5259176] 剩余票數(shù):46 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}
2018-02-23 22:25:36.198993+0800 YSC-GCD-demo[20712:5259176] 剩余票數(shù):45 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}
...
可以看到在不考慮線程安全吟策,不使用 semaphore 的情況下儒士,得到票數(shù)是錯(cuò)亂的,這樣顯然不符合我們的需求檩坚,所以我們需要考慮線程安全問題着撩。
6.6.2.2 線程安全(使用 semaphore 加鎖)
考慮線程安全的代碼:
/**
線程安全:使用 semaphore 加鎖
初始化火車票數(shù)量、賣票窗口(線程安全)匾委、并開始賣票
*/
-
(void)initTicketStatusSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當(dāng)前線程
NSLog(@"semaphore---begin");
semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
// queue1 代表北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上和闲穑火車票售賣窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
/**
- 售賣火車票(線程安全)
*/
-
(void)saleTicketSafe {
while (1) {
// 相當(dāng)于加鎖 dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER); if (self.ticketSurplusCount > 0) { //如果還有票相寇,繼續(xù)售賣 self.ticketSurplusCount--; NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]); [NSThread sleepForTimeInterval:0.2]; } else { //如果已賣完绽媒,關(guān)閉售票窗口 NSLog(@"所有火車票均已售完"); // 相當(dāng)于解鎖 dispatch_semaphore_signal(semaphoreLock); break; } // 相當(dāng)于解鎖 dispatch_semaphore_signal(semaphoreLock);
}
}
輸出結(jié)果為:
2018-02-23 22:32:19.814232+0800 YSC-GCD-demo[20862:5290531] currentThread---<NSThread: 0x6000000783c0>{number = 1, name = main}
2018-02-23 22:32:19.814412+0800 YSC-GCD-demo[20862:5290531] semaphore---begin
2018-02-23 22:32:19.814837+0800 YSC-GCD-demo[20862:5290687] 剩余票數(shù):49 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
2018-02-23 22:32:20.017745+0800 YSC-GCD-demo[20862:5290689] 剩余票數(shù):48 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:20.222039+0800 YSC-GCD-demo[20862:5290687] 剩余票數(shù):47 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
...
2018-02-23 22:32:29.024817+0800 YSC-GCD-demo[20862:5290689] 剩余票數(shù):4 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:29.230110+0800 YSC-GCD-demo[20862:5290687] 剩余票數(shù):3 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
2018-02-23 22:32:29.433615+0800 YSC-GCD-demo[20862:5290689] 剩余票數(shù):2 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:29.637572+0800 YSC-GCD-demo[20862:5290687] 剩余票數(shù):1 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
2018-02-23 22:32:29.840234+0800 YSC-GCD-demo[20862:5290689] 剩余票數(shù):0 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:30.044960+0800 YSC-GCD-demo[20862:5290687] 所有火車票均已售完
2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] 所有火車票均已售完
可以看出痴柔,在考慮了線程安全的情況下颜骤,使用 dispatch_semaphore
機(jī)制之后妙黍,得到的票數(shù)是正確的锣夹,沒有出現(xiàn)混亂的情況浪南。我們也就解決了多個(gè)線程同步的問題父款。
參考資料:
iOS多線程詳盡總結(jié)系列文章:
- 本文作者: 行走的少年郎
- 版權(quán)聲明: 本文章采用 CC BY-NC-SA 3.0 許可協(xié)議斩松。轉(zhuǎn)載請(qǐng)注明出處!
? 著作權(quán)歸作者所有
舉報(bào)文章
關(guān)注