一餐胀、GCD簡介
GCD全稱是Grand Central Dispatch
,是Apple開發(fā)中用于實現(xiàn)多線程編程的一種解決方案。GCD是純C
語言,提供了很多強(qiáng)大的函數(shù)。
GCD的優(yōu)勢如下:
- GCD 可用于多核的并行運(yùn)算囤耳;
- GCD 會自動利用更多的CPU內(nèi)核(比如雙核、四核)偶芍;
- GCD 會自動管理線程的生命周期(創(chuàng)建線程充择、調(diào)度任務(wù)、銷毀線程)匪蟀;
- 代碼簡潔椎麦,不需要編寫任何線程管理的代碼。
二材彪、GCD的任務(wù)和隊列
GCD中有兩個核心概念:『任務(wù)』和『隊列』观挎。
1.任務(wù)
任務(wù)即執(zhí)行操作琴儿,簡單來說是在線程上執(zhí)行的那段代碼,在GCD中被封裝在block
中键兜。任務(wù)的執(zhí)行方式有兩種:『同步執(zhí)行』凤类,『異步執(zhí)行』。
同步執(zhí)行(
sync
)
- 不會開啟新線程普气,只在當(dāng)前線程中串行執(zhí)行任務(wù)谜疤,需要等待。
- 對于代碼執(zhí)行來說现诀,必須等待當(dāng)前語句執(zhí)?完畢夷磕,才會執(zhí)?下?條語句。
異步執(zhí)行(
async
)
- 會開啟新線程仔沿,并發(fā)執(zhí)行任務(wù)坐桩,不需等待。
- 對于代碼執(zhí)行來說封锉,不?等待當(dāng)前語句執(zhí)?完畢绵跷,就可以執(zhí)?下?條語句。
2.隊列(Dispatch Queue)
這里的隊列指執(zhí)行任務(wù)的等待隊列成福,即用來存放任務(wù)的隊列碾局。隊列是一種特殊的線性表,采用FIFO(先進(jìn)先出)
的原則奴艾,即最早被添加的任務(wù)會先被調(diào)度執(zhí)行净当。每執(zhí)行一個任務(wù),就從隊列中釋放一個任務(wù)蕴潦。隊列的結(jié)構(gòu)可參考下圖:
GCD中的隊列也分為兩種:『串行隊列』像啼,『并發(fā)隊列』。
-
串行隊列(
Serial Dispatch Queue
):只開啟一個線程潭苞,同一時間只執(zhí)行一個任務(wù)忽冻,一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)萄传。 -
并發(fā)隊列(
Concurrent Dispatch Queue
):可以開啟多個線程甚颂,同一時間可以并發(fā)執(zhí)行多個任務(wù),不用等待秀菱。
注意:并發(fā)隊列中的任務(wù)只有在異步執(zhí)行下才會開啟多個線程振诬,同步執(zhí)行下只會在當(dāng)前線程中處理任務(wù)。
兩者的任務(wù)執(zhí)行情況如下圖所示:
三衍菱、GCD的使用步驟
GCD使用步驟教簡單赶么,只有兩步:
1. 創(chuàng)建一個隊列(串行隊列或者并行隊列)。
2. 創(chuàng)建任務(wù)脊串,將任務(wù)添加到隊列(創(chuàng)建時需指定是同步執(zhí)行還是異步執(zhí)行)辫呻。
1.隊列的創(chuàng)建和獲取
-
創(chuàng)建串行隊列和并發(fā)隊列
通過
dispatch_queue_create
函數(shù)可以創(chuàng)建串行隊列和并發(fā)隊列清钥,函數(shù)需要傳入兩個參數(shù):- 參數(shù)1:為隊列的標(biāo)識符,可以為空放闺;
- 參數(shù)2:為隊列的類型祟昭,
DISPATCH_QUEUE_SERIAL
表示串行隊列,DISPATCH_QUEUE_CONCURRENT
表示并發(fā)隊列怖侦。其中DISPATCH_QUEUE_SERIAL
本質(zhì)是NULL
篡悟。
//創(chuàng)建一個串行隊列,參數(shù)2也可以直接傳NULL dispatch_queue_t sQueue = dispatch_queue_create("lab1", DISPATCH_QUEUE_SERIAL); //創(chuàng)建一個并發(fā)隊列 dispatch_queue_t cQueue = dispatch_queue_create("lab2", DISPATCH_QUEUE_CONCURRENT); #define DISPATCH_QUEUE_SERIAL NULL
-
獲取主隊列(Main Dispatch Queue)
主隊列是一種特殊的串行隊列匾寝,程序啟動時由系統(tǒng)默認(rèn)提供搬葬,所有任務(wù)均默認(rèn)添加在主隊列。主隊列中所有的任務(wù)艳悔,無論是同步執(zhí)行還是異步執(zhí)行急凰,都只會在主線程中執(zhí)行。通過
dispatch_get_main_queue()
函數(shù)來獲取主隊列:dispatch_queue_t mainQueue = dispatch_get_main_queue();
-
獲取全局并發(fā)隊列(Global Dispatch Queue)
在使?多線程開發(fā)時猜年,如果對隊列沒有特殊需求抡锈,在執(zhí)?異步任務(wù)時,可以直接使?全局并發(fā)隊列乔外。通過
dispatch_get_global_queue
函數(shù)來獲取全局并發(fā)隊列企孩,需要傳入兩個參數(shù):- 參數(shù)1:表示隊列優(yōu)先級,一般傳入
DISPATCH_QUEUE_PRIORITY_DEFAULT
袁稽。 - 參數(shù)2:暫時沒用,傳
0
即可擒抛。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 參數(shù)1:表示隊列優(yōu)先級,一般傳入
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ù)代碼
});
四、對串行隊列的分析
串行隊列里的任務(wù)歧沪,會遵循FIFO規(guī)則
依次調(diào)度執(zhí)行歹撒,由于任務(wù)的執(zhí)行方式分為同步執(zhí)行和異步執(zhí)行,所以串行隊列中的任務(wù)存在以下的情況:
- 全部都是同步執(zhí)行的任務(wù)
- 全部都是異步執(zhí)行的任務(wù)
- 既有同步執(zhí)行又有異步執(zhí)行的任務(wù)
- 線程死鎖
接下來通過具體示例來分析這幾種組合情況诊胞。
1. 全部都是同步執(zhí)行的任務(wù)
如下面代碼所示暖夭,往一個串行隊列中添加三個同步執(zhí)行任務(wù),然后打印執(zhí)行結(jié)果撵孤。
//串行隊列+同步執(zhí)行任務(wù)
-(void)testSerialAndSync{
NSLog(@" ==== method thread = %@", [NSThread currentThread]);
NSLog(@" ==== begin");
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
//添加同步執(zhí)行任務(wù)1
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)2
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)3
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
});
NSLog(@" ==== end");
}
//打印結(jié)果
2020-11-15 01:16:07.499128+0800 GCDDemo[17257:70356957] ==== method thread = <NSThread: 0x600001704100>{number = 1, name = main}
2020-11-15 01:16:07.499199+0800 GCDDemo[17257:70356957] ==== begin
2020-11-15 01:16:09.500480+0800 GCDDemo[17257:70356957] ==== task1 thread = <NSThread: 0x600001704100>{number = 1, name = main}
2020-11-15 01:16:11.501599+0800 GCDDemo[17257:70356957] ==== task2 thread = <NSThread: 0x600001704100>{number = 1, name = main}
2020-11-15 01:16:13.502912+0800 GCDDemo[17257:70356957] ==== task3 thread = <NSThread: 0x600001704100>{number = 1, name = main}
2020-11-15 01:16:13.503060+0800 GCDDemo[17257:70356957] ==== end
從打印結(jié)果可知迈着,三個任務(wù)均在當(dāng)前線程(主線程)里按順序依次執(zhí)行。因此邪码,當(dāng)一個串行隊列里都是同步執(zhí)行的任務(wù)裕菠,不會開辟新線程,所有任務(wù)均在當(dāng)前線程同步執(zhí)行闭专。
2. 全部都是異步執(zhí)行的任務(wù)
如下面代碼所示奴潘,往一個串行隊列中添加三個異步執(zhí)行任務(wù)旧烧,然后打印執(zhí)行結(jié)果。
//串行隊列+異步執(zhí)行任務(wù)
-(void)testSerialAndAsync{
NSLog(@" ==== method thread = %@", [NSThread currentThread]);
NSLog(@" ==== begin");
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
//添加異步執(zhí)行任務(wù)1
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
});
//添加異步執(zhí)行任務(wù)2
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
});
//添加異步執(zhí)行任務(wù)3
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
});
NSLog(@" ==== end");
}
//打印結(jié)果
2020-11-16 10:12:25.065872+0800 GCDDemo[41263:73331602] ==== method thread = <NSThread: 0x60000170c100>{number = 1, name = main}
2020-11-16 10:12:25.065953+0800 GCDDemo[41263:73331602] ==== begin
2020-11-16 10:12:25.066017+0800 GCDDemo[41263:73331602] ==== end
2020-11-16 10:12:28.071048+0800 GCDDemo[41263:73331984] ==== task1 thread = <NSThread: 0x60000175c2c0>{number = 2, name = (null)}
2020-11-16 10:12:30.074008+0800 GCDDemo[41263:73331984] ==== task2 thread = <NSThread: 0x60000175c2c0>{number = 2, name = (null)}
2020-11-16 10:12:31.076458+0800 GCDDemo[41263:73331984] ==== task3 thread = <NSThread: 0x60000175c2c0>{number = 2, name = (null)}
從打印結(jié)果可知画髓,這個過程中只新開啟了一條子線程掘剪,三個任務(wù)在子線程中按序依次執(zhí)行。因此奈虾,當(dāng)一個串行隊列里全是異步執(zhí)行的任務(wù)夺谁,只會開啟一個新線程,所有任務(wù)在這個線程里按順序同步執(zhí)行愚墓。
3. 既有同步執(zhí)行又有異步執(zhí)行的任務(wù)
如下面代碼所示予权,往一個串行隊列中穿插添加兩個同步執(zhí)行的任務(wù)和兩個異步執(zhí)行的任務(wù)。
//串行隊列+兩種方式執(zhí)行的任務(wù)
-(void)testSerial{
NSLog(@" ==== method thread = %@", [NSThread currentThread]);
NSLog(@" ==== begin");
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
//添加異步執(zhí)行任務(wù)1
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)2
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
});
//添加異步執(zhí)行任務(wù)3
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)4
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@" ==== task4 thread = %@", [NSThread currentThread]);
});
NSLog(@" ==== end");
}
//打印結(jié)果
2020-11-16 14:25:03.502639+0800 GCDDemo[56034:75334852] ==== method thread = <NSThread: 0x600001704140>{number = 1, name = main}
2020-11-16 14:25:03.502730+0800 GCDDemo[56034:75334852] ==== begin
2020-11-16 14:25:07.503842+0800 GCDDemo[56034:75335420] ==== task1 thread = <NSThread: 0x600001749a80>{number = 2, name = (null)}
2020-11-16 14:25:10.505354+0800 GCDDemo[56034:75334852] ==== task2 thread = <NSThread: 0x600001704140>{number = 1, name = main}
2020-11-16 14:25:12.506879+0800 GCDDemo[56034:75335420] ==== task3 thread = <NSThread: 0x600001749a80>{number = 2, name = (null)}
2020-11-16 14:25:13.508375+0800 GCDDemo[56034:75334852] ==== task4 thread = <NSThread: 0x600001704140>{number = 1, name = main}
2020-11-16 14:25:13.508554+0800 GCDDemo[56034:75334852] ==== end
從打印結(jié)果可知浪册,這個過程中新開啟了一條子線程扫腺,兩個異步任務(wù)在子線程執(zhí)行,兩個同步任務(wù)在當(dāng)前線程(主線程)中執(zhí)行村象,且這4個任務(wù)是按順序依次執(zhí)行笆环。因此,當(dāng)一個串行隊列中既有同步執(zhí)行任務(wù)厚者,還有異步執(zhí)行任務(wù)時躁劣,會新開啟一個子線程,所有異步任務(wù)均在子線程上執(zhí)行库菲,所有同步任務(wù)均在當(dāng)前線程中執(zhí)行账忘,雖然是在兩個線程上執(zhí)行,但所有任務(wù)依舊按序同步執(zhí)行熙宇。
4. 線程死鎖
當(dāng)往一個串行隊列中添加同步執(zhí)行任務(wù)時鳖擒,若使用不當(dāng),可能會導(dǎo)致線程死鎖烫止,這里對普通串行隊列和主隊列來分別進(jìn)行分析蒋荚。
- 普通串行隊列死鎖
-(void)testSerialAndSyncLock{
NSLog(@" ==== method thread = %@", [NSThread currentThread]);
NSLog(@" ==== begin");
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
//添加異步執(zhí)行任務(wù)1
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
//添加同步執(zhí)行任務(wù)2
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
});
});
NSLog(@" ==== end");
}
//打印結(jié)果
2020-11-16 14:48:47.797169+0800 GCDDemo[57469:75514015] ==== method thread = <NSThread: 0x600001708640>{number = 1, name = main}
2020-11-16 14:48:47.797242+0800 GCDDemo[57469:75514015] ==== begin
2020-11-16 14:48:47.797304+0800 GCDDemo[57469:75514015] ==== end
2020-11-16 14:48:49.802076+0800 GCDDemo[57469:75514581] ==== task1 thread = <NSThread: 0x600001757dc0>{number = 2, name = (null)}
//報錯
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
dispatch_sync_f_slow
在這個例子中,先往串行隊列queue中添加一個異步執(zhí)行任務(wù)task1馆蠕,然后在task1里又往隊列中添加同步執(zhí)行任務(wù)task2期升。從前面的分析可知,task1會在子線程中執(zhí)行互躬,task2會在主線程中執(zhí)行播赁,但從打印結(jié)果可知,子線程死鎖了吨铸。
為什么會這樣呢行拢?從代碼邏輯可知,串行隊列queue里的任務(wù)情況如下圖所示:
由于是串行隊列,所以需要先執(zhí)行完task1舟奠,才能執(zhí)行task2竭缝。上文已說明,任務(wù)其實是寫在block里的代碼沼瘫,所以task1里具體包含sleepForTimeInterval
抬纸、NSLog
、dispatch_sync
這三個操作耿戚,而執(zhí)行dispatch_sync
操作湿故,必須要先執(zhí)行其block里的任務(wù)(也就是task2),所以這里完成task1的執(zhí)行需要先等待task2執(zhí)行完膜蛔,而task2的執(zhí)行又必須等待task1的完成坛猪,這樣就造成了兩個任務(wù)互相等待的局面,導(dǎo)致串行隊列阻塞皂股,使得正在執(zhí)行任務(wù)的子線程死鎖墅茉。
- 主隊列死鎖
-(void)testMainQueueAndSyncLock{
NSLog(@" ==== method thread = %@", [NSThread currentThread]);
NSLog(@" ==== begin");
dispatch_queue_t queue = dispatch_get_main_queue();
//添加同步執(zhí)行任務(wù)1
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
});
NSLog(@" ==== end");
}
//執(zhí)行結(jié)果
2020-11-16 15:10:25.390210+0800 GCDDemo[58693:75674415] ==== method thread = <NSThread: 0x600001709040>{number = 1, name = main}
2020-11-16 15:10:25.390282+0800 GCDDemo[58693:75674415] ==== begin
//報錯
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
dispatch_sync_f_slow
這里主線程死鎖的原因和上面一樣,雖然說task1里沒有添加其他同步執(zhí)行任務(wù)呜呐,但是外層方法testMainQueueAndSyncLock
本身就是添加在主隊列里的任務(wù)就斤,方法里包含了dispatch_sync
操作,所以需要先執(zhí)行block里的任務(wù)(即task1)蘑辑。而這個dispatch_sync
操作是將task1添加到主隊列中洋机,所以又需要先等外層方法testMainQueueAndSyncLock
執(zhí)行完才能執(zhí)行task1,這樣就造成外層方法和task1互相等待洋魂,阻塞了主隊列绷旗,導(dǎo)致主線程死鎖。
- 主隊列+同步執(zhí)行不一定會死鎖
這里要特別說明一下副砍,之前有看過其他文章刁标,說主隊列+同步執(zhí)行
一定會造成死鎖,其實是不準(zhǔn)確的址晕,只是因為“執(zhí)行主隊列+同步執(zhí)行
的任務(wù)”本身也處在主隊列中同步執(zhí)行,所以會導(dǎo)致主線程死鎖顿锰。來看下面的例子:
-(void)testMainQueueAndSyncLock{
NSLog(@" ==== method thread = %@", [NSThread currentThread]);
NSLog(@" ==== begin");
dispatch_queue_t queue = dispatch_queue_create("lab", DISPATCH_QUEUE_SERIAL);
//添加異步執(zhí)行任務(wù)1
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
//添加同步執(zhí)行任務(wù)2到主隊列中
dispatch_sync(dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
});
});
//這里延遲8s
[NSThread sleepForTimeInterval:8];
NSLog(@" ==== end");
}
//打印結(jié)果
2020-11-16 15:22:38.153895+0800 GCDDemo[59402:75766119] ==== method thread = <NSThread: 0x600001708280>{number = 1, name = main}
2020-11-16 15:22:38.153978+0800 GCDDemo[59402:75766119] ==== begin
2020-11-16 15:22:40.156534+0800 GCDDemo[59402:75766421] ==== task1 thread = <NSThread: 0x60000174b440>{number = 2, name = (null)}
2020-11-16 15:22:46.154269+0800 GCDDemo[59402:75766119] ==== end
2020-11-16 15:22:48.247564+0800 GCDDemo[59402:75766119] ==== task2 thread = <NSThread: 0x600001708280>{number = 1, name = main}
這里先往一個串行隊列queue中添加一個異步執(zhí)行任務(wù)task1谨垃,再在tsak1里往主隊列中添加同步執(zhí)行任務(wù)task2,從執(zhí)行結(jié)果可知硼控,并未死鎖。通過代碼邏輯可知,在這個過程中存在兩個隊列:主隊列和串行隊列queue酥诽。串行隊列queue中只有一個異步執(zhí)行的任務(wù)task1爽锥,不會造成死鎖,所以主要看主隊列中的情況熏版。
主隊列里存在兩個同步執(zhí)行任務(wù)纷责,會先執(zhí)行任務(wù)外層方法testMainQueueAndSyncLock
捍掺,再執(zhí)行task2。外層方法里包含了NSLog
再膳、sleepForTimeInterval
挺勿、dispatch_async
這幾個操作,而執(zhí)行dispatch_async
的操作不需要先將其block里的任務(wù)執(zhí)行完喂柒,所以可以直接將外層方法執(zhí)行完不瓶,然后再執(zhí)行另一個任務(wù)task2,這樣兩個任務(wù)不會互相等待灾杰,也就不會導(dǎo)致主線程死鎖蚊丐。
5. 串行隊列分析后的結(jié)論
根據(jù)上面的分析可知,串行隊列在各場景下的使用情況如下:
- 當(dāng)一個串行隊列里都是同步執(zhí)行的任務(wù)艳吠,不會開辟新線程麦备,所有任務(wù)均在當(dāng)前線程同步執(zhí)行。
- 當(dāng)一個串行隊列里全是異步執(zhí)行的任務(wù)讲竿,只會開啟一個新線程泥兰,所有任務(wù)在這個線程里按順序同步執(zhí)行。
- 當(dāng)一個串行隊列中既有同步執(zhí)行任務(wù)题禀,還有異步執(zhí)行任務(wù)時鞋诗,會新開啟一個子線程,所有異步任務(wù)均在子線程上執(zhí)行迈嘹,所有同步任務(wù)均在當(dāng)前線程中執(zhí)行削彬,雖然是在兩個線程上執(zhí)行,但所有任務(wù)依舊按序同步執(zhí)行秀仲。
- 若在某個串行隊列中使用
當(dāng)前串行隊列+同步執(zhí)行任務(wù)
融痛,則會阻塞當(dāng)前隊列,導(dǎo)致當(dāng)前線程死鎖神僵。
五雁刷、對并發(fā)隊列的分析
并發(fā)隊列也遵循FIFO原則
,和串行隊列一樣保礼,也會存在以下幾種情況:
- 全部都是同步執(zhí)行的任務(wù)
- 全部都是同步執(zhí)行的任務(wù)
- 既有同步執(zhí)行又有異步執(zhí)行的任務(wù)
接下來也通過具體示例來分析這幾種組合情況沛励。
1. 全部都是同步執(zhí)行的任務(wù)
如下面代碼所示,往一個并發(fā)隊列中添加三個同步執(zhí)行任務(wù)炮障,然后打印執(zhí)行結(jié)果目派。
//并發(fā)隊列+同步執(zhí)行任務(wù)
-(void)testConcurrentAndSync{
NSLog(@" ==== method thread = %@", [NSThread currentThread]);
NSLog(@" ==== begin");
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
//添加同步執(zhí)行任務(wù)1
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)2
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)3
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
});
NSLog(@" ==== end");
}
2020-11-16 16:03:40.559502+0800 GCDDemo[61734:76074945] ==== method thread = <NSThread: 0x60000170ce40>{number = 1, name = main}
2020-11-16 16:03:40.559576+0800 GCDDemo[61734:76074945] ==== begin
2020-11-16 16:03:43.559757+0800 GCDDemo[61734:76074945] ==== task1 thread = <NSThread: 0x60000170ce40>{number = 1, name = main}
2020-11-16 16:03:45.560417+0800 GCDDemo[61734:76074945] ==== task2 thread = <NSThread: 0x60000170ce40>{number = 1, name = main}
2020-11-16 16:03:46.560727+0800 GCDDemo[61734:76074945] ==== task3 thread = <NSThread: 0x60000170ce40>{number = 1, name = main}
2020-11-16 16:03:46.560801+0800 GCDDemo[61734:76074945] ==== end
從打印結(jié)果可知,這三個任務(wù)均在主線程中按序執(zhí)行胁赢。因此企蹭,當(dāng)一個并發(fā)隊列里都是同步執(zhí)行的任務(wù),不會開辟新線程,所有任務(wù)均在當(dāng)前線程同步執(zhí)行谅摄。
2. 全部都是異步執(zhí)行的任務(wù)
如下面代碼所示徒河,往一個并發(fā)隊列中添加三個異步執(zhí)行任務(wù),然后打印執(zhí)行結(jié)果螟凭。
//并發(fā)隊列+異步執(zhí)行任務(wù)
-(void)testConcurrentAndAsync{
NSLog(@" ==== method thread = %@", [NSThread currentThread]);
NSLog(@" ==== begin");
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
//添加同步執(zhí)行任務(wù)1
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)2
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)3
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
});
NSLog(@" ==== end");
}
//打印結(jié)果
2020-11-16 16:08:30.368449+0800 GCDDemo[62020:76112767] ==== method thread = <NSThread: 0x600001708600>{number = 1, name = main}
2020-11-16 16:08:30.368531+0800 GCDDemo[62020:76112767] ==== begin
2020-11-16 16:08:30.368594+0800 GCDDemo[62020:76112767] ==== end
2020-11-16 16:08:31.369037+0800 GCDDemo[62020:76113117] ==== task3 thread = <NSThread: 0x60000176a980>{number = 2, name = (null)}
2020-11-16 16:08:32.372892+0800 GCDDemo[62020:76113114] ==== task2 thread = <NSThread: 0x60000176c2c0>{number = 3, name = (null)}
2020-11-16 16:08:33.373147+0800 GCDDemo[62020:76113116] ==== task1 thread = <NSThread: 0x600001767340>{number = 4, name = (null)}
從打印結(jié)果可知虚青,三個任務(wù)在三條不同的線程上異步執(zhí)行。因此螺男,當(dāng)一個并發(fā)隊列里都是異步執(zhí)行任務(wù)時棒厘,會給每一個任務(wù)開啟一條新線程,來異步執(zhí)行這些任務(wù)下隧。
3. 既有同步執(zhí)行又有異步執(zhí)行的任務(wù)
如下面代碼所示奢人,往一個并發(fā)隊列中穿插添加兩個同步執(zhí)行的任務(wù)和兩個異步執(zhí)行的任務(wù)。
//并發(fā)隊列+同步和異步執(zhí)行任務(wù)
-(void)testConcurrent{
NSLog(@" ==== method thread = %@", [NSThread currentThread]);
NSLog(@" ==== begin");
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
//添加異步執(zhí)行任務(wù)1
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@" ==== task1 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)2
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@" ==== task2 thread = %@", [NSThread currentThread]);
});
//添加異步執(zhí)行任務(wù)3
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task3 thread = %@", [NSThread currentThread]);
});
//添加同步執(zhí)行任務(wù)4
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@" ==== task4 thread = %@", [NSThread currentThread]);
});
NSLog(@" ==== end");
}
//打印結(jié)果
2020-11-16 16:32:19.942009+0800 GCDDemo[63414:76291148] ==== method thread = <NSThread: 0x600001704e80>{number = 1, name = main}
2020-11-16 16:32:19.942079+0800 GCDDemo[63414:76291148] ==== begin
2020-11-16 16:32:22.942793+0800 GCDDemo[63414:76291148] ==== task2 thread = <NSThread: 0x600001704e80>{number = 1, name = main}
2020-11-16 16:32:23.944156+0800 GCDDemo[63414:76291650] ==== task1 thread = <NSThread: 0x600001742b00>{number = 2, name = (null)}
2020-11-16 16:32:24.943348+0800 GCDDemo[63414:76291148] ==== task4 thread = <NSThread: 0x600001704e80>{number = 1, name = main}
2020-11-16 16:32:24.943353+0800 GCDDemo[63414:76291576] ==== task3 thread = <NSThread: 0x600001742b80>{number = 3, name = (null)}
2020-11-16 16:32:24.943470+0800 GCDDemo[63414:76291148] ==== end
從打印結(jié)果可知淆院,兩個同步任務(wù)均在主線程中按序執(zhí)行何乎,兩個異步任務(wù)在兩個不同的子線程中執(zhí)行(打印結(jié)果的順序后面再分析)。因此土辩,當(dāng)一個并發(fā)隊列中既有同步執(zhí)行任務(wù)支救,還有異步執(zhí)行任務(wù)時,會給每一個異步任務(wù)開啟一條新線程來異步執(zhí)行拷淘,而所有同步任務(wù)均在當(dāng)前線程中按順序執(zhí)行各墨。
4. 并發(fā)隊列分析后的結(jié)論
根據(jù)上面的分析可知,串行隊列在各場景下的使用情況如下:
- 當(dāng)一個并發(fā)隊列里都是同步執(zhí)行的任務(wù)启涯,不會開辟新線程贬堵,所有任務(wù)均在當(dāng)前線程同步執(zhí)行。
- 當(dāng)一個并發(fā)隊列里都是異步執(zhí)行任務(wù)時结洼,會給每一個任務(wù)開啟一條新線程黎做,來異步執(zhí)行這些任務(wù)
- 當(dāng)一個并發(fā)隊列中既有同步執(zhí)行任務(wù),還有異步執(zhí)行任務(wù)時松忍,會給每一個異步任務(wù)開啟一條新線程來異步執(zhí)行蒸殿,而所有同步任務(wù)均在當(dāng)前線程中按順序執(zhí)行。
六鸣峭、同步函數(shù)和異步函數(shù)的耗時情況
對相同復(fù)雜度的任務(wù)來說伟桅,dispacth_sync
和dispatch_async
執(zhí)行時間是不一樣的。
- dispacth_async
-(void)testAsync{
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@" ==== async interval = %f", CFAbsoluteTimeGetCurrent() - time);
});
NSLog(@" ==== interval = %f", CFAbsoluteTimeGetCurrent() - time);
}
//打印結(jié)果
2020-11-16 19:05:31.131300+0800 GCDDemo[72585:77510232] ==== interval = 0.000019
2020-11-16 19:05:31.131308+0800 GCDDemo[72585:77510854] ==== async interval = 0.000029
從打印結(jié)果可以看出叽掘,dispatch_async
函數(shù)雖然先執(zhí)行,但執(zhí)行相同復(fù)雜度任務(wù)所需的時間玖雁,還是比直接執(zhí)行要久更扁。
- dispacth_async和dispatch_sync
-(void)testAsyncAndSync{
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@" ==== async interval = %f", CFAbsoluteTimeGetCurrent() - time);
});
dispatch_sync(queue, ^{
NSLog(@" ==== sync interval = %f", CFAbsoluteTimeGetCurrent() - time);
});
}
//打印結(jié)果
2020-11-16 19:09:08.919239+0800 GCDDemo[72810:77540305] ==== sync interval = 0.000033
2020-11-16 19:09:08.919398+0800 GCDDemo[72810:77540590] ==== async interval = 0.000214
從打印結(jié)果可知,執(zhí)行相同復(fù)雜度的任務(wù)時,dispatch_async
要比dispatch_sync
所需時間更長浓镜。
六溃列、面試題解析
學(xué)習(xí)了前面的知識后,接下來對幾個高頻次面試題的輸出結(jié)果進(jìn)行解析膛薛。
1. 并發(fā)隊列 + 異步執(zhí)行
-(void)interview1{
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
NSLog(@" ==== 1");
dispatch_async(queue, ^{
NSLog(@" ==== 2");
dispatch_async(queue, ^{
NSLog(@" ==== 3");
});
NSLog(@" ==== 4");
});
NSLog(@" ==== 5");
}
//打印結(jié)果
2020-11-16 19:17:51.449472+0800 GCDDemo[73315:77606414] ==== 1
2020-11-16 19:17:51.449562+0800 GCDDemo[73315:77606414] ==== 5
2020-11-16 19:17:51.449575+0800 GCDDemo[73315:77606769] ==== 2
2020-11-16 19:17:51.449634+0800 GCDDemo[73315:77606769] ==== 4
2020-11-16 19:17:51.449646+0800 GCDDemo[73315:77606768] ==== 3
這道題比較簡單听隐,分析思路:先整體,再局部哄啄。
- 首先雅任,從整體上看,外層方法
interview1
是在主線程中執(zhí)行咨跌,里面的操作按順序執(zhí)行沪么,主要包含:NSLog(@" ==== 1")
,dispatch_async
锌半、NSLog(@" ==== 5")
禽车。由于dispatch_async
是異步執(zhí)行無需等待,且比直接執(zhí)行形同復(fù)雜度的任務(wù)耗時更久刊殉,所以會先輸出1殉摔,5,再輸出dispatch_async
函數(shù)block里的打印记焊。 - 再看
dispatch_async
里的任務(wù)逸月,里面包含三個操作:NSLog(@" ==== 2")
、dispatch_async
亚亲、NSLog(@" ==== 4")
彻采。這三個操作在新開啟的子線程里按順序執(zhí)行,和interview1
里的操作一樣捌归,輸出順序為:2肛响、4、3惜索。
因此特笋,經(jīng)過分析得知,這道題的輸出順序為:1巾兆、5猎物、2、4角塑、3蔫磨。
這里將并發(fā)隊列改為串行隊列,輸出順序還是一樣圃伶,大家可以自己分析下堤如。
2. 并發(fā)隊列 + 異步函數(shù)嵌套同步函數(shù)
-(void)interview2{
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
NSLog(@" ==== 1");
dispatch_async(queue, ^{
NSLog(@" ==== 2");
dispatch_sync(queue, ^{
NSLog(@" ==== 3");
});
NSLog(@" ==== 4");
});
NSLog(@" ==== 5");
}
//打印結(jié)果
2020-11-16 19:44:38.915026+0800 GCDDemo[74906:77810083] ==== 1
2020-11-16 19:44:38.915117+0800 GCDDemo[74906:77810083] ==== 5
2020-11-16 19:44:38.915127+0800 GCDDemo[74906:77810255] ==== 2
2020-11-16 19:44:38.915168+0800 GCDDemo[74906:77810255] ==== 3
2020-11-16 19:44:38.915201+0800 GCDDemo[74906:77810255] ==== 4
這道題只是在上一題上做個小改動蒲列,將里面的dispatch_async
改為dispatch_sync
。所以分析思路還是一樣:先整體搀罢,再局部蝗岖。
- 從整體上看,還是先輸出1榔至、5抵赢,再輸出
dispatch_async
函數(shù)block里的任務(wù)。 - 從
dispatch_async
里的任務(wù)來看唧取,三個操作NSLog(@" ==== 2")
铅鲤、dispatch_sync
以及NSLog(@" ==== 4")
在新開的子線程中同步執(zhí)行,所以先輸出2兵怯,而dispatch_sync
是同步函數(shù)彩匕,所以需要等待其block里的任務(wù)執(zhí)行完,才能執(zhí)行后面的任務(wù)媒区,所以先輸出3驼仪,再輸出4。
因此袜漩,經(jīng)過分析得知绪爸,這道題的輸出順序為:1、5宙攻、2奠货、3、4座掘。
3. 串行隊列 + 異步函數(shù)嵌套同步函數(shù)
-(void)interview3{
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL);
NSLog(@" ==== 1");
dispatch_async(queue, ^{
NSLog(@" ==== 2");
dispatch_sync(queue, ^{
NSLog(@" ==== 3");
});
NSLog(@" ==== 4");
});
NSLog(@" ==== 5");
}
//打印結(jié)果
2020-11-16 20:06:27.263232+0800 GCDDemo[76170:77980426] ==== 1
2020-11-16 20:06:27.263325+0800 GCDDemo[76170:77980426] ==== 5
2020-11-16 20:06:27.263338+0800 GCDDemo[76170:77980465] ==== 2
//報錯
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
dispatch_sync_f_slow
這里輸出1递惋、5、2后就報錯溢陪,因為任務(wù)互相等待萍虽,阻塞了串行隊列queue,導(dǎo)致子線程死鎖了(具體分析查在上文的『普通串行隊列死鎖』)形真。
4. 并發(fā)隊列 + 異步函數(shù)嵌套同步函數(shù)
-(void)interview4{
dispatch_queue_t queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@" ==== 1");
});
dispatch_async(queue, ^{
NSLog(@" ==== 2");
});
dispatch_sync(queue, ^{
NSLog(@" ==== 3");
});
NSLog(@" ==== 0");
dispatch_async(queue, ^{
NSLog(@" ==== 7");
});
dispatch_async(queue, ^{
NSLog(@" ==== 8");
});
dispatch_async(queue, ^{
NSLog(@" ==== 9");
});
}
//打印結(jié)果
2020-11-16 21:36:24.898847+0800 GCDDemo[81805:78779526] ==== 3
2020-11-16 21:36:24.898907+0800 GCDDemo[81805:78779885] ==== 2
2020-11-16 21:36:24.898880+0800 GCDDemo[81805:78779883] ==== 1
2020-11-16 21:36:24.898930+0800 GCDDemo[81805:78779526] ==== 0
2020-11-16 21:36:24.898997+0800 GCDDemo[81805:78779883] ==== 7
2020-11-16 21:36:24.899024+0800 GCDDemo[81805:78779885] ==== 8
2020-11-16 21:36:24.899045+0800 GCDDemo[81805:78779883] ==== 9
這里的輸出結(jié)果只是其中的一種情況杉编,還有其他情況。從前面對并發(fā)隊列分析后的結(jié)論可知咆霜,當(dāng)一個并發(fā)隊列中既有同步執(zhí)行任務(wù)邓馒,還有異步執(zhí)行任務(wù)時,會給每一個異步任務(wù)開啟一條新線程來異步執(zhí)行蛾坯,而所有同步任務(wù)均在當(dāng)前線程中按順序執(zhí)行光酣。因此這題的分析思路:先同步,再異步脉课。
-
3
和0
的輸出是處于外層方法interview4
中的同步執(zhí)行任務(wù)救军,所以0必定會在3后輸出改览。 -
7
、8
缤言、9
三個輸出任務(wù)的dispatch_async
操作是在外層方法interview4
中同步執(zhí)行的,且在同步任務(wù)NSLog(@" ==== 0")
后面视事,所以7胆萧、8、9必定在0后輸出俐东。 -
1
跌穗、2
、7
虏辫、8
蚌吸、9
這五個輸出任務(wù)均在并發(fā)隊列queue中,且會在不同的子線程上異步執(zhí)行砌庄,不需要等待羹唠,所以它們的執(zhí)行不分先后。
因此娄昆,經(jīng)過分析可知佩微,0必定在3的后面輸出,7萌焰、8哺眯、9必定在0的后面輸出,而根據(jù)任務(wù)的復(fù)雜度不同扒俯,1奶卓,2在任何位置出現(xiàn)都有可能。