隊列
概念:隊列只負責(zé)任務(wù)的調(diào)度,而不負責(zé)任務(wù)的執(zhí)行,任務(wù)是在線程中執(zhí)行的。(可以理解成任務(wù)是放在隊列里面的,要被調(diào)度到線程中去執(zhí)行)
特點:隊列先進先出,排在前面的任務(wù)最先執(zhí)行。
分類:隊列分為串行、并行、主隊列、全局隊列。
- 串行隊列:任務(wù)按照順序被調(diào)度,前一個任務(wù)不執(zhí)行完畢,隊列不會調(diào)度。
- 并行隊列:只要有空閑的線程,隊列就會調(diào)度當(dāng)前任務(wù),交給線程去執(zhí)行,不需要考慮前面是都有任務(wù)在執(zhí)行,只要有線程可以利用,隊列就會調(diào)度任務(wù)。
- 主隊列:只負責(zé)把任務(wù)調(diào)度到主線程去執(zhí)行。所以主隊列的任務(wù)都要在主線程來執(zhí)行,主隊列會隨著程序的啟動一起創(chuàng)建,我們只需get即可。
- 全局隊列:是系統(tǒng)為了方便程序員開發(fā)提供的,其工作表現(xiàn)與并發(fā)隊列一致。
任務(wù)的執(zhí)行是在線程上去執(zhí)行的。分為同步和異步。
- 同步:不會開啟新的線程,任務(wù)按順序執(zhí)行,會阻塞當(dāng)前線程。
- 異步:會開啟新的線程,任務(wù)可以并發(fā)的執(zhí)行。
所以就可以分成:串行隊列同步執(zhí)行、串行隊列異步執(zhí)行、并行隊列同步執(zhí)行、并行隊列異步執(zhí)行。
- 串行隊列同步執(zhí)行:按順序執(zhí)行并不會開啟新的線程,則串行隊列同步執(zhí)行只是按部就班的one by one執(zhí)行。
- 串行隊列異步執(zhí)行:雖然隊列中存放的是異步執(zhí)行的任務(wù),但是結(jié)合串行隊列的特點,前一個任務(wù)不執(zhí)行完畢,隊列不會調(diào)度,所以串行隊列異步執(zhí)行也是one by one的執(zhí)行
- 并行隊列同步執(zhí)行:結(jié)合上面闡述的并行隊列的特點,和同步執(zhí)行的特點,可以明確的分析出來,雖然并行隊列可以不需等待前一個任務(wù)執(zhí)行完畢就可調(diào)度下一個任務(wù),但是任務(wù)同步執(zhí)行不會開啟新的線程,所以任務(wù)也是one by one的執(zhí)行
- 并行隊列異步執(zhí)行:再上一條中說明了并行隊列的特點,而異步執(zhí)行是任務(wù)可以開啟新的線程,所以這中組合可以實現(xiàn)任務(wù)的并發(fā),再實際開發(fā)中也是經(jīng)常會用到的。
GCD實現(xiàn)原理:
GCD有一個底層線程池,這個池中存放的是一個個的線程。之所以稱為“池”,是因為這個“池”中的線程是可以重用的,當(dāng)一段時間后沒有任務(wù)在這個線程上執(zhí)行的話,這個線程就會被銷毀。注意:開多少條線程是由底層線程池決定的(線程建議控制再3~5條),池是系統(tǒng)自動來維護,不需要我們程序員來維護。
我們只關(guān)心的是向隊列中添加任務(wù),隊列調(diào)度即可。
- 如果隊列中存放的是同步任務(wù),則任務(wù)出隊后,底層線程池中只會提供一條線程來執(zhí)行這個任務(wù),任務(wù)執(zhí)行完畢后這條線程再回到線程池。這樣隊列中的任務(wù)反復(fù)調(diào)度,因為是同步的,所以當(dāng)我們用currentThread打印的時候,就是同一條線程。
- 如果隊列中存放的是異步的任務(wù),(注意異步可以開線程),當(dāng)任務(wù)出隊后,底層線程池會提供一個線程供任務(wù)執(zhí)行,因為是異步執(zhí)行,隊列中的任務(wù)不需等待當(dāng)前任務(wù)執(zhí)行完畢就可以調(diào)度下一個任務(wù),這時底層線程池中會再次提供一個線程供第二個任務(wù)執(zhí)行,執(zhí)行完畢后再回到底層線程池中。(可能第三個任務(wù)出列后,第一條任務(wù)恰好也已經(jīng)執(zhí)行完畢,這時候就不需要重新開啟一條新的線程了,直接復(fù)用執(zhí)行第一個任務(wù)的線程即可,從而節(jié)約系統(tǒng)的資源)。
- 因為線程可以復(fù)用,所以就不需要每一個任務(wù)執(zhí)行都開啟新的線程,也就從而節(jié)約了系統(tǒng)的開銷,提高了效率。在iOS7.0的時候,使用GCD系統(tǒng)通常只能開5-8條線程,iOS8.0以后,系統(tǒng)可以開啟很多條線程,但是實在開發(fā)應(yīng)用中,建議開啟線程條數(shù):3-5條最為合理。
死鎖
定義:調(diào)用方法(viewDidLoad)的隊列(主隊列)恰好是同步操作(dispatch_sync)所針對的隊列(dispatch_get_main_queue)。
示例1:
NSLog(@"1"); // 任務(wù)1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務(wù)2
});
NSLog(@"3"); // 任務(wù)3
輸出結(jié)果:
1
dispatch_sync 和 dispatch_async 區(qū)別:
dispatch_async(queue,block) async 異步隊列,dispatch_async 函數(shù)會立即返回, block會在后臺異步執(zhí)行。
dispatch_sync(queue,block) sync 同步隊列,dispatch_sync 函數(shù)不會立即返回,及阻塞當(dāng)前線程,等待 block同步執(zhí)行完成。
以上例子就會死鎖,因為viewDidLoad的這個任務(wù)是被主隊列調(diào)用的的,而dispatch_sync不會立即返回,而是先阻塞當(dāng)前的主線程,直到這個block執(zhí)行完畢,因為主線程被阻礙了,啥也干不了了(只有一個線程還被阻塞了,就會造成死鎖),所以這個block就永遠沒有機會執(zhí)行了,所以就會造成死鎖。
示例2:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
dispatch_async(dispatch_get_main_queue(0, 0), ^{
NSLog(@"2");
});
NSLog(@"3");
}
輸出結(jié)果:
1
3
2
示例2就不會造成死鎖,因為dispatch_async會立即返回,所以會先輸出3,而異步會創(chuàng)建一個新的線程來執(zhí)行block塊,所以2最后輸出。但是2和3的順序不一定。
示例3:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(@"1");//任務(wù)1
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");//任務(wù)2
});
NSLog(@"3");//任務(wù)3
}
輸出結(jié)果:
1
2
3
示例3也不會造成死鎖,因為dispatch_sync不會立即返回,而是先阻塞主線程,再將任務(wù)2加入到一個全局隊列的一個線程上去執(zhí)行,執(zhí)行完之后返回到主隊列,此時主線程不在阻塞,再繼續(xù)執(zhí)行任務(wù)3。
示例4:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(@"1");//任務(wù)1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");//任務(wù)2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");//任務(wù)3
});
NSLog(@"5");//任務(wù)5
});
NSLog(@"4");//任務(wù)4
}
輸出結(jié)果:
1
4
2
3
5
因為dispatch_async不會等待,所以順序是1-4-2-3-5或1-2-4-3-5,其中任務(wù)1和4是在主線程執(zhí)行的,而2是在全局隊列上被調(diào)用的,執(zhí)行完2之后,會阻塞當(dāng)前的線程(全局隊列上的),緊接著會回到主隊列上的主線程上執(zhí)行任務(wù)3,任務(wù)3執(zhí)行完之后,會繼續(xù)執(zhí)行5,此時全局隊列上的線程也不堵塞了。
注意:線程同步阻塞后不一定能造成死鎖,還要看看還有沒有其他線程去執(zhí)行那個block,如果能有,就能解鎖阻塞的線程,繼續(xù)執(zhí)行任務(wù)。如果沒有,那就是死鎖了。
示例5:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("test", NULL);
NSLog(@"1");
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
輸出結(jié)果:
1
5
2
死鎖
最終結(jié)果還是會導(dǎo)致死鎖,因為dispatch_queue_create創(chuàng)建隊列的時候傳入NULL默認是串行隊列,所以執(zhí)行任務(wù)2之后,會阻塞掉當(dāng)前線程,直到任務(wù)3的block執(zhí)行完成,又因為當(dāng)前線程被阻塞掉了,block也無法執(zhí)行,導(dǎo)致相互等待造成死鎖
示例6:
- (void)viewDidLoad {
[super viewDidLoad];
while (self.num < 5) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
self.num ++;
});
}
NSLog(@"?:%ld", self.num);
}
輸出結(jié)果:>=5
因為self.num++操作是異步的,不一定能立馬返回結(jié)果,所以在進入下次while循環(huán)的時候,self.num(主線程)可能還是0,所以循環(huán)肯定至少5次,最理想的情況下,5次全部都返回結(jié)果,而NSLog是會等待異步結(jié)果返回才會打印,所以輸出結(jié)果>=5
示例7:
for (NSInteger i = 0; i < 1000; i ++) {
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, DISPATCH_QUEUE_CONCURRENT), ^{
self.num ++;
});
}
NSLog(@"self.num:%ld", self.num);
輸出結(jié)果為:<1000
因為總共循環(huán)1000次,并不是每次結(jié)果都有返回,所以最終打印的self.num肯定小于1000