除了上邊提到的『主線程』中調(diào)用『主隊(duì)列』+『同步執(zhí)行』會(huì)導(dǎo)致死鎖問題祈秕。實(shí)際在使用『串行隊(duì)列』的時(shí)候,也可能出現(xiàn)阻塞『串行隊(duì)列』所在線程的情況發(fā)生雏胃,從而造成死鎖問題请毛。這種情況多見于同一個(gè)串行隊(duì)列的嵌套使用。
比如下面代碼這樣:在『異步執(zhí)行』+『串行隊(duì)列』的任務(wù)中瞭亮,又嵌套了『當(dāng)前的串行隊(duì)列』方仿,然后進(jìn)行『同步執(zhí)行』。
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ // 異步執(zhí)行 + 串行隊(duì)列
dispatch_sync(queue, ^{ // 同步執(zhí)行 + 當(dāng)前串行隊(duì)列
// 追加任務(wù) 1
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
});
執(zhí)行上面的代碼會(huì)導(dǎo)致 串行隊(duì)列中追加的任務(wù) 和 串行隊(duì)列中原有的任務(wù) 兩者之間相互等待统翩,阻塞了『串行隊(duì)列』仙蚜,最終造成了串行隊(duì)列所在的線程(子線程)死鎖問題。
主隊(duì)列造成死鎖也是基于這個(gè)原因厂汗,所以委粉,這也進(jìn)一步說明了主隊(duì)列其實(shí)并不特殊。
關(guān)于 『隊(duì)列中嵌套隊(duì)列』這種復(fù)雜情況娶桦,這里也簡(jiǎn)單做一個(gè)總結(jié)贾节。不過這里只考慮同一個(gè)隊(duì)列的嵌套情況汁汗,關(guān)于多個(gè)隊(duì)列的相互嵌套情況還請(qǐng)自行研究,或者等我最新的文章發(fā)布栗涂。
『不同隊(duì)列』+『不同任務(wù)』 組合知牌,以及 『隊(duì)列中嵌套隊(duì)列』 使用的區(qū)別:
好了,關(guān)于『不同隊(duì)列』+『不同任務(wù)』 組合不同區(qū)別總結(jié)就到這里斤程。
3.5 關(guān)于不同隊(duì)列和不同任務(wù)的形象理解
因?yàn)榍耙欢螘r(shí)間看到了有朋友留言說對(duì) 異步執(zhí)行
和 并發(fā)隊(duì)列
中創(chuàng)建線程能力有所不理解角寸,我覺得這個(gè)問題的確很容易造成困惑,所以很值得拿來專門分析一下忿墅。'
他的問題:
在 異步 + 并發(fā) 中的解釋:
(異步執(zhí)行具備開啟新線程的能力扁藕。且并發(fā)隊(duì)列可開啟多個(gè)線程,同時(shí)執(zhí)行多個(gè)任務(wù))
以及 同步 + 并發(fā) 中的解釋:
(雖然并發(fā)隊(duì)列可以開啟多個(gè)線程球匕,并且同時(shí)執(zhí)行多個(gè)任務(wù)纹磺。但是因?yàn)楸旧聿荒軇?chuàng)建新線程,只有當(dāng)前線程這一個(gè)線程(同步任務(wù)不具備開啟新線程的能力)
這個(gè)地方看起來有點(diǎn)疑惑亮曹,你兩個(gè)地方分別提到:異步執(zhí)行開啟新線程橄杨,并發(fā)隊(duì)列也可以開啟新線程,想請(qǐng)教下照卦,你的意思是只有任務(wù)才擁有創(chuàng)建新線程的能力式矫,而隊(duì)列只有開啟線程的能力,并不能創(chuàng)建線程 役耕?這二者是這樣的關(guān)聯(lián)嗎采转?
關(guān)于這個(gè)問題,我想做一個(gè)很形象的類比瞬痘,來幫助大家對(duì) 隊(duì)列故慈、任務(wù) 以及 線程 之間關(guān)系的理解。
假設(shè)現(xiàn)在有 5 個(gè)人要穿過一道門禁框全,這道門禁總共有 10 個(gè)入口察绷,管理員可以決定同一時(shí)間打開幾個(gè)入口,可以決定同一時(shí)間讓一個(gè)人單獨(dú)通過還是多個(gè)人一起通過津辩。不過默認(rèn)情況下拆撼,管理員只開啟一個(gè)入口,且一個(gè)通道一次只能通過一個(gè)人喘沿。
這個(gè)故事里闸度,人好比是 任務(wù),管理員好比是 系統(tǒng)蚜印,入口則代表 線程莺禁。
5 個(gè)人表示有 5 個(gè)任務(wù),10 個(gè)入口代表 10 條線程晒哄。
串行隊(duì)列 好比是 5 個(gè)人排成一支長(zhǎng)隊(duì)睁宰。
并發(fā)隊(duì)列 好比是 5 個(gè)人排成多支隊(duì)伍肪获,比如 2 隊(duì),或者 3 隊(duì)柒傻。
同步任務(wù) 好比是管理員只開啟了一個(gè)入口(當(dāng)前線程)孝赫。
異步任務(wù) 好比是管理員同時(shí)開啟了多個(gè)入口(當(dāng)前線程 + 新開的線程)。
『異步執(zhí)行 + 并發(fā)隊(duì)列』 可以理解為:現(xiàn)在管理員開啟了多個(gè)入口(比如 3 個(gè)入口)红符,5 個(gè)人排成了多支隊(duì)伍(比如 3 支隊(duì)伍)青柄,這樣這 5 個(gè)人就可以 3 個(gè)人同時(shí)一起穿過門禁了。
『同步執(zhí)行 + 并發(fā)隊(duì)列』 可以理解為:現(xiàn)在管理員只開啟了 1 個(gè)入口预侯,5 個(gè)人排成了多支隊(duì)伍致开。雖然這 5 個(gè)人排成了多支隊(duì)伍,但是只開了 1 個(gè)入口啊萎馅,這 5 個(gè)人雖然都想快點(diǎn)過去双戳,但是 1 個(gè)入口一次只能過 1 個(gè)人,所以大家就只好一個(gè)接一個(gè)走過去了糜芳,表現(xiàn)的結(jié)果就是:順次通過入口飒货。
- 換成 GCD 里的語(yǔ)言就是說:
*『異步執(zhí)行 + 并發(fā)隊(duì)列』就是:系統(tǒng)開啟了多個(gè)線程(主線程+其他子線程),任務(wù)可以多個(gè)同時(shí)運(yùn)行峭竣。
*『同步執(zhí)行 + 并發(fā)隊(duì)列』就是:系統(tǒng)只默認(rèn)開啟了一個(gè)主線程塘辅,沒有開啟子線程,雖然任務(wù)處于并發(fā)隊(duì)列中皆撩,但也只能一個(gè)接一個(gè)執(zhí)行了扣墩。
同步執(zhí)行+并發(fā)隊(duì)列
是默認(rèn)開啟了一個(gè)主線程,沒有開啟子線程扛吞,雖然任務(wù)處于并發(fā)隊(duì)列中呻惕,但也只能一個(gè)接一個(gè)執(zhí)行了
下邊我們來研究一下上邊提到的六種簡(jiǎn)單組合方式的使用方法。
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
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_sync(queue, ^{
// 追加任務(wù) 2
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_sync(queue, ^{
// 追加任務(wù) 3
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
NSLog(@"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
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_async(queue, ^{
// 追加任務(wù) 2
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_async(queue, ^{
// 追加任務(wù) 3
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
NSLog(@"asyncConcurrent---end");
}
在 異步執(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
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_sync(queue, ^{
// 追加任務(wù) 2
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_sync(queue, ^{
// 追加任務(wù) 3
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
NSLog(@"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
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_async(queue, ^{
// 追加任務(wù) 2
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_async(queue, ^{
// 追加任務(wù) 3
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
NSLog(@"asyncSerial---end");
}
在 異步執(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ì)列:GCD 默認(rèn)提供的 串行隊(duì)列嚼吞。
默認(rèn)情況下盒件,平常所寫代碼是直接放在主隊(duì)列中的。
所有放在主隊(duì)列中的任務(wù)舱禽,都會(huì)放到主線程中執(zhí)行炒刁。
可使用
dispatch_get_main_queue()
獲得主隊(duì)列。
我們?cè)賮砜纯粗麝?duì)列的兩種組合方式誊稚。
4.5 同步執(zhí)行 + 主隊(duì)列
同步執(zhí)行 + 主隊(duì)列
在不同線程中調(diào)用結(jié)果也是不一樣翔始,在主線程中調(diào)用會(huì)發(fā)生死鎖問題,而在其他線程中調(diào)用則不會(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
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_sync(queue, ^{
// 追加任務(wù) 2
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
dispatch_sync(queue, ^{
// 追加任務(wù) 3
[NSThread sleepForTimeInterval:2]; // 模擬耗時(shí)操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當(dāng)前線程
});
NSLog(@"syncMain---end");
}
在主線程中使用 同步執(zhí)行 + 主隊(duì)列
可以驚奇的發(fā)現(xiàn):