主要內(nèi)容:
1危纫、隊(duì)列的認(rèn)識(shí)
2虫溜、任務(wù)的認(rèn)識(shí)
3、隊(duì)列和任務(wù)搭配使用的面試題
4耙厚、死鎖的認(rèn)識(shí)和解決
隊(duì)列
隊(duì)列就是管理待執(zhí)行任務(wù)的等待隊(duì)列渠缕,用來(lái)調(diào)度任務(wù)給線程執(zhí)行勾哩,符合先進(jìn)先出原則
并發(fā)和串行決定了任務(wù)執(zhí)行的順序股耽,并發(fā)是多個(gè)任務(wù)并發(fā)執(zhí)行根盒,串行是指任務(wù)順序執(zhí)行,也就是一個(gè)任務(wù)執(zhí)行完成再執(zhí)行下一個(gè)任務(wù)
- 隊(duì)列負(fù)責(zé)調(diào)度任務(wù)物蝙,提交任務(wù)給線程執(zhí)行
- 隊(duì)列遵循先進(jìn)先出原則,在這里指的是先進(jìn)先調(diào)度敢艰。而不是先調(diào)度完成诬乞。
- 隊(duì)列底層會(huì)維護(hù)一個(gè)線程池來(lái)處理用戶提交的任務(wù),線程池的作用就是執(zhí)行隊(duì)列管理的任務(wù)钠导。
- 串行隊(duì)列底層只維護(hù)了一個(gè)線程震嫉,并發(fā)隊(duì)列的底層維護(hù)了多個(gè)線程
- 串行隊(duì)列一次只能處理一個(gè)任務(wù),上一個(gè)任務(wù)處理完成才能處理下一個(gè)任務(wù)
- 并發(fā)隊(duì)列可以同時(shí)處理多個(gè)任務(wù)牡属,雖然處理順序還是先進(jìn)先出的原則票堵,但是有的任務(wù)處理時(shí)間比較長(zhǎng),有可能先進(jìn)后調(diào)度完成逮栅。
- 隊(duì)列是先進(jìn)先調(diào)度悴势,如果是串行隊(duì)列是先進(jìn)先調(diào)度結(jié)束,并發(fā)隊(duì)列并不是先進(jìn)先調(diào)度結(jié)束措伐,可以同時(shí)調(diào)度多個(gè)任務(wù)
創(chuàng)建隊(duì)列:
- (void)createQueueTest{
//創(chuàng)建隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("串行隊(duì)列", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("并發(fā)隊(duì)列", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue3 = dispatch_queue_create("串行隊(duì)列", NULL);
//獲取已有隊(duì)列
//獲取主隊(duì)列
dispatch_queue_t queue4 = dispatch_get_main_queue();
//獲取全局并發(fā)隊(duì)列
/*
第一個(gè)參數(shù)是優(yōu)先級(jí)特纤,第二個(gè)無(wú)意義,填0
*/
dispatch_queue_t queue5 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
}
任務(wù)
任務(wù)就是線程要執(zhí)行的那段代碼
執(zhí)行任務(wù)有兩種方式侥加,同步和異步捧存,同步和可以異步?jīng)Q定是否要開(kāi)啟新的線程,同步不開(kāi)啟担败,異步開(kāi)啟昔穴。
同步函數(shù)(sync):
- 同步函數(shù)用來(lái)實(shí)現(xiàn)線程同步執(zhí)行任務(wù)
- 只能在當(dāng)前線程中執(zhí)行任務(wù),不具備開(kāi)啟新線程的能力
- 同步函數(shù)只能等block執(zhí)行完成后才能返回
異步函數(shù)(async):
- 異步函數(shù)用來(lái)實(shí)現(xiàn)線程異步執(zhí)行任務(wù)
- 異步函數(shù)不需要等block執(zhí)行完成后就可以返回
- 可以開(kāi)啟新線程
提交任務(wù)的函數(shù)很多都有兩個(gè)版本提前,一個(gè)是傳兩個(gè)參數(shù)吗货,傳遞作為任務(wù)的block代碼,一個(gè)是傳三個(gè)參數(shù)岖研,傳遞作為任務(wù)的方法卿操。
- (void)createTaskTest{
/*
參數(shù)一:隊(duì)列
參數(shù)二:作為任務(wù)的block代碼
*/
//在調(diào)度隊(duì)列上提交異步執(zhí)行的塊并立即返回
dispatch_async(dispatch_queue_t _Nonnull queue, <#^(void)block#>);
/*
參數(shù)一:隊(duì)列
參數(shù)二:上下文
參數(shù)三:作為任務(wù)的函數(shù)
*/
dispatch_async_f(<#dispatch_queue_t _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t _Nonnull work#>);
/*
參數(shù)一:隊(duì)列
參數(shù)二:作為任務(wù)的block代碼
*/
//提交塊對(duì)象以執(zhí)行,并在該塊完成執(zhí)行后返回孙援。
dispatch_sync(dispatch_queue_t _Nonnull queue, <#^(void)block#>);
/*
參數(shù)一:隊(duì)列
參數(shù)二:上下文
參數(shù)三:作為任務(wù)的函數(shù)
*/
dispatch_sync_f(<#dispatch_queue_t _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t _Nonnull work#>);
}
注意:
- 任務(wù)是線程執(zhí)行的害淤,不是隊(duì)列執(zhí)行,隊(duì)列只是用來(lái)調(diào)度任務(wù)的
- 任務(wù)的執(zhí)行方式有同步和異步拓售,同步不開(kāi)辟新線程窥摄,異步會(huì)開(kāi)辟新線程
主隊(duì)列
主隊(duì)列是一種特殊的串行隊(duì)列,特殊在于只使用在主線程和主Runloop中础淤,并且是在啟動(dòng)APP時(shí)自動(dòng)創(chuàng)建的崭放。
主隊(duì)列是指主線程的隊(duì)列哨苛,是一個(gè)串行隊(duì)列,在主線程去執(zhí)行其實(shí)就是放入了主隊(duì)列
全局并發(fā)隊(duì)列
全局并發(fā)隊(duì)列是系統(tǒng)提供的币砂,開(kāi)發(fā)者可以直接使用的一個(gè)并發(fā)隊(duì)列建峭,沒(méi)有其他的特殊之處。
在獲取時(shí)可以給定優(yōu)先級(jí)
- DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED
- DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY
- DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
總結(jié)
- 線程有兩種方式執(zhí)行任務(wù)决摧,同步和異步亿蒸,分別通過(guò)同步函數(shù)和異步函數(shù)來(lái)實(shí)現(xiàn)
- 同步函數(shù)添加的任務(wù),該線程執(zhí)行完這個(gè)任務(wù)才可以執(zhí)行其他任務(wù)掌桩,異步函數(shù)添加的任務(wù)边锁,線程可以并發(fā)執(zhí)行該任務(wù)
- 任務(wù)存放在隊(duì)列中,隊(duì)列有兩種調(diào)度方式波岛,串行和并發(fā)茅坛,串行只能順序處理任務(wù),并發(fā)可以同時(shí)處理多個(gè)任務(wù)
- 線程想要并發(fā)執(zhí)行任務(wù)则拷,需要異步函數(shù)添加任務(wù)給并發(fā)隊(duì)列
- 如果隊(duì)列是串行的贡蓖,即使線程可以并發(fā)執(zhí)行任務(wù)也不行,因?yàn)殛?duì)列是串行調(diào)度給線程的隔躲。
- 同步函數(shù)需要等待block執(zhí)行完成才可以返回
- 異步函數(shù)不需要等待block執(zhí)行完成就可以返回
隊(duì)列和任務(wù)的搭配使用
說(shuō)明:
- 同步不開(kāi)啟新線程摩梧,異步會(huì)開(kāi)啟新線程
- 并發(fā)隊(duì)列可以并發(fā)調(diào)度任務(wù),串行隊(duì)列只能順序調(diào)度任務(wù)
- 只有并發(fā)隊(duì)列提交給線程異步執(zhí)行的任務(wù)才可以異步執(zhí)行
總結(jié):
隊(duì)列和任務(wù)的常見(jiàn)類型代碼演示
1宣旱、基礎(chǔ)寫法
給一個(gè)串行隊(duì)列添加了一個(gè)任務(wù)仅父,該任務(wù)需要異步執(zhí)行
- (void)syncTest{
// 把任務(wù)添加到隊(duì)列 --> 函數(shù)
// 任務(wù) _t ref c對(duì)象
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
//串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("wy", NULL);
// 函數(shù)
dispatch_async(queue, block);
}
2、主隊(duì)列同步
- (void)mainSyncTest{
NSLog(@"0");
// 等
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"1");
});
NSLog(@"2");
}
執(zhí)行結(jié)果: 0浑吟,之后死鎖
說(shuō)明:
- 在主隊(duì)列中添加了三個(gè)任務(wù)笙纤,任務(wù)0,同步任務(wù)塊组力,任務(wù)2
- 同步任務(wù)塊的任務(wù)本身就是給主隊(duì)列添加了任務(wù)1
- 因此現(xiàn)在主隊(duì)列的任務(wù)有四個(gè)省容,并且順序?yàn)槿蝿?wù)0、同步任務(wù)塊燎字、任務(wù)2腥椒、任務(wù)1,這幾個(gè)任務(wù)依次執(zhí)行
- 同步任務(wù)塊需要等待任務(wù)1的完成才能返回候衍,但是任務(wù)1的完成又在同步任務(wù)塊的后面
- 所以造成了同步任務(wù)塊和任務(wù)1的相互等待笼蛛,死鎖導(dǎo)致程序崩潰
注意:造成死鎖是同步任務(wù)塊和任務(wù)1的相互等待,與任務(wù)2沒(méi)關(guān)系蛉鹿,就算刪掉任務(wù)2滨砍,也會(huì)死鎖
3、 主隊(duì)列異步
- (void)mainAsyncTest{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"1");
});
NSLog(@"2");
}
執(zhí)行結(jié)果: 21
說(shuō)明:
- 主隊(duì)列中添加兩個(gè)任務(wù),異步任務(wù)塊和任務(wù)2
- 異步任務(wù)塊的任務(wù)本身是給主隊(duì)列添加了一個(gè)任務(wù)1
- 這樣主隊(duì)列有三個(gè)任務(wù)惋戏,并且順序?yàn)楫惒饺蝿?wù)塊领追、任務(wù)2、任務(wù)1
- 因?yàn)槿蝿?wù)1是異步提交的响逢,也就是線程以異步的方式來(lái)執(zhí)行绒窑,異步任務(wù)塊不需要等block執(zhí)行完成就可以返回
- 因此這里不會(huì)發(fā)生死鎖
4、 全局并發(fā)隊(duì)列異步
并發(fā)執(zhí)行任務(wù)
- (void)globalAsyncTest{
for (int i = 0; i<20; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
結(jié)果:
2021-10-31 14:58:16.038522+0800 多線程使用[11062:5684222] hello queue
2021-10-31 14:58:16.038559+0800 多線程使用[11062:5684360] 0-<NSThread: 0x600003d438c0>{number = 6, name = (null)}
2021-10-31 14:58:16.038564+0800 多線程使用[11062:5684368] 2-<NSThread: 0x600003d34440>{number = 7, name = (null)}
2021-10-31 14:58:16.038567+0800 多線程使用[11062:5684359] 3-<NSThread: 0x600003d28780>{number = 4, name = (null)}
2021-10-31 14:58:16.038571+0800 多線程使用[11062:5684358] 1-<NSThread: 0x600003d282c0>{number = 8, name = (null)}
2021-10-31 14:58:16.038585+0800 多線程使用[11062:5684361] 4-<NSThread: 0x600003d31b80>{number = 3, name = (null)}
2021-10-31 14:58:16.038606+0800 多線程使用[11062:5684362] 5-<NSThread: 0x600003d43e00>{number = 5, name = (null)}
2021-10-31 14:58:16.038642+0800 多線程使用[11062:5684360] 7-<NSThread: 0x600003d438c0>{number = 6, name = (null)}
說(shuō)明:
- 異步執(zhí)行會(huì)開(kāi)啟新線程舔亭,因此有多個(gè)線程來(lái)執(zhí)行
- 在并發(fā)隊(duì)列中異步執(zhí)行代碼塊回论,因此執(zhí)行順序是異步的
- 并且打印hello是最早執(zhí)行的,這是因?yàn)楫惒讲l(fā)執(zhí)行分歇,異步添加任務(wù)消耗的時(shí)間比較多,所以先執(zhí)行hello
5欧漱、 全局并發(fā)隊(duì)列同步
串行執(zhí)行任務(wù)
- (void)globalSyncTest{
for (int i = 0; i<20; i++) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
結(jié)果:
2021-10-31 14:55:53.552472+0800 多線程使用[10971:5681912] 13-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552541+0800 多線程使用[10971:5681912] 14-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552635+0800 多線程使用[10971:5681912] 15-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552729+0800 多線程使用[10971:5681912] 16-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.552829+0800 多線程使用[10971:5681912] 17-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.559220+0800 多線程使用[10971:5681912] 18-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.559332+0800 多線程使用[10971:5681912] 19-<_NSMainThread: 0x600000a2c7c0>{number = 1, name = main}
2021-10-31 14:55:53.559401+0800 多線程使用[10971:5681912] hello queue
說(shuō)明:
- 主隊(duì)列中有同步任務(wù)塊和hello兩個(gè)任務(wù)职抡,同步任務(wù)塊添加了多個(gè)任務(wù)到并發(fā)隊(duì)列中
- 因?yàn)槭峭胶瘮?shù),所以不會(huì)開(kāi)啟新線程误甚,只有一個(gè)主線程來(lái)執(zhí)行
- 并且打印hello是在最后執(zhí)行的缚甩,這是因?yàn)橥酱a塊是在hello任務(wù)的前面,同步函數(shù)需要等代碼塊執(zhí)行完成后再返回窑邦,所以需要等同步代碼塊執(zhí)行完才能執(zhí)行hello
簡(jiǎn)單面試題分析
面試題1
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
結(jié)果: 15243
說(shuō)明:
- 在主隊(duì)列中包含三個(gè)任務(wù)擅威,同步任務(wù)1、異步任務(wù)代碼塊冈钦、同步任務(wù)5
- 異步任務(wù)塊不需要等待函數(shù)執(zhí)行完成就可以返回郊丛,而異步函數(shù)本身是耗時(shí)的,所以前面的順序是1瞧筛、5
- 在異步任務(wù)中將三個(gè)任務(wù)添加到一個(gè)并發(fā)隊(duì)列中厉熟,包括任務(wù)2,同步代碼塊较幌、任務(wù)4
- 因?yàn)槭遣l(fā)隊(duì)列異步提交任務(wù)揍瑟,所以這三個(gè)任務(wù)并發(fā)執(zhí)行,
- 但因?yàn)殛?duì)列的先進(jìn)先出原則乍炉,先進(jìn)的先調(diào)用绢片,再看消耗的時(shí)間,同步代碼塊會(huì)消耗更多的時(shí)間岛琼,所以最后執(zhí)行
- 任務(wù)2和任務(wù)4執(zhí)行時(shí)間一樣底循,但是任務(wù)2先執(zhí)行
- 所以順序?yàn)?、4衷恭、3此叠,合起來(lái)就是15243
面試題2
- (void)textDemo{
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
// 耗時(shí)
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
結(jié)果: 1、5、2灭袁,崩潰
說(shuō)明:
- 在主隊(duì)列中包含三個(gè)任務(wù)猬错,同步任務(wù)1、異步任務(wù)代碼塊茸歧、同步任務(wù)5
- 主隊(duì)列是串行的倦炒,所以調(diào)度順序是任務(wù)1、代碼塊软瞎、任務(wù)5
- 異步任務(wù)塊不需要等block執(zhí)行完就可以返回逢唤,所以順序是1、5
- 代碼塊中將三個(gè)任務(wù)添加到一個(gè)并發(fā)隊(duì)列中涤浇,包括任務(wù)2鳖藕,同步代碼塊、任務(wù)4
- 在串行隊(duì)列中只锭,包含任務(wù)2和同步任務(wù)塊著恩、任務(wù)4、任務(wù)3蜻展,而同步任務(wù)塊的執(zhí)行結(jié)束需要等待任務(wù)3的結(jié)束
- 造成死鎖喉誊,因此在執(zhí)行完3后會(huì)崩潰
注意:
這里雖然是主隊(duì)列,任務(wù)塊在任務(wù)5的前面纵顾,但是在執(zhí)行時(shí)我們看到先執(zhí)行了任務(wù)5后執(zhí)行了任務(wù)2伍茄,
這是因?yàn)楫惒讲僮鞑⒉恍枰却瘮?shù)的執(zhí)行完成而完成,
面試題3
- (void)wbinterDemo{
dispatch_queue_t queue11 = dispatch_queue_create("wy", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue11, ^{
NSLog(@"1");
});
dispatch_async(queue11, ^{
NSLog(@"2");
});
dispatch_sync(queue11, ^{
NSLog(@"3");
});
NSLog(@"0");
dispatch_async(queue11, ^{
NSLog(@"1");
});
dispatch_async(queue11, ^{
NSLog(@"7");
});
dispatch_async(queue11, ^{
NSLog(@"8");
});
dispatch_async(queue11, ^{
NSLog(@"9");
});
}
請(qǐng)選擇:
A:1230789
B:1237890
C:3120798
D:2137890
答案為AC
說(shuō)明:
- 首先這些任務(wù)塊操作都是在主隊(duì)列中施逾,其中任務(wù)3不會(huì)開(kāi)啟新線程敷矫,和任務(wù)0都是主線程執(zhí)行的,其他的任務(wù)在并發(fā)隊(duì)列中音念,會(huì)開(kāi)啟新線程執(zhí)行沪饺,同步任務(wù)塊必須等任務(wù)3執(zhí)行完才能繼續(xù)往下執(zhí)行,所以任務(wù)3肯定在任務(wù)0前面闷愤。(簡(jiǎn)單認(rèn)識(shí)整葡,同步并發(fā)是串行,所以任務(wù)3在任務(wù)0前面)
- 7讥脐、8遭居、9任務(wù)在任務(wù)0后添加,所以會(huì)在0任務(wù)后面執(zhí)行
- 同時(shí)7旬渠、8俱萍、9任務(wù)都是異步并發(fā),所以并發(fā)執(zhí)行告丢,沒(méi)有順序
- 任務(wù)1枪蘑、2在任務(wù)3之前添加到并發(fā)隊(duì)列中,也就是任務(wù)1和2與3在不同的隊(duì)列中,而任務(wù)1和2又是異步并發(fā)的岳颇,所以他們?nèi)齻€(gè)會(huì)異步并發(fā)執(zhí)行照捡,他們?nèi)齻€(gè)是沒(méi)有順序的。
死鎖的認(rèn)識(shí)
死鎖就是同一個(gè)串行隊(duì)列中兩個(gè)任務(wù)相互等待引起死鎖.
網(wǎng)上面很多說(shuō)是不同的線程相互等待引起的话侧,是解釋不通的栗精,應(yīng)該是隊(duì)列,因?yàn)殛?duì)列是先進(jìn)先出原則導(dǎo)致瞻鹏,而后面進(jìn)入的任務(wù)的結(jié)束又依賴于前面進(jìn)的任務(wù)悲立。
一個(gè)串行隊(duì)列的線程池只維護(hù)了一個(gè)線程。
死鎖的場(chǎng)景
主隊(duì)列同步執(zhí)行
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任務(wù)1");
});
}
說(shuō)明:
- 在主隊(duì)列中有同步任務(wù)塊新博,之后添加任務(wù)1到主隊(duì)列中薪夕。
- 而同步任務(wù)塊的執(zhí)行完成需要等block執(zhí)行完成,所以同步任務(wù)塊的執(zhí)行依賴于任務(wù)1
- 而在串行隊(duì)列中赫悄,任務(wù)1的完成又需要同步任務(wù)塊的完成寥殖。
- 由此同步任務(wù)塊和任務(wù)1相互等待,造成了死鎖
同步串行隊(duì)列的嵌套場(chǎng)景也是一樣的涩蜘,不再多言
- (void)textDemo{
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
// 耗時(shí)
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
死鎖的解決
上面知道死鎖是一個(gè)串行隊(duì)列中有兩個(gè)任務(wù)相互等待導(dǎo)致的,所以有兩個(gè)解決辦法熏纯,
- 隊(duì)列為并發(fā)同诫,不是串行
- 異步任務(wù),取消一方的等待樟澜,也就打破相互等待
所以可以有兩個(gè)做法误窖,
將隊(duì)列改為并發(fā)
- (void)textDemo1{
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
將任務(wù)改為異步任務(wù),及不會(huì)相互等待
- (void)textDemo{
dispatch_queue_t queue = dispatch_queue_create("cooci", NULL);
NSLog(@"1");
// 耗時(shí)
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}