iOS開發中,多線程的解決方案有四種:pthread
,NSThread
,GCD
,NSOperation
,對于我自己來說GCD用的最多,而這篇文章只負責通過幾個GCD
案例幫我們更好的理解線程隊列,以及線程同步異步的關系,關于其他線程解決方案的原理和應用就不多贅述了。
一、GCD的特點
- GCD會自動利用更多的CPU內核。
- GCD自動管理線程的生命周期(創建線程,調度任務,毀掉線程等)。
- 程序員只需要告訴GCD想要如何執行什么任務,不需要編寫任何線程管理代碼。
二、GCD的基本概念
任務
block
:任務就是將要在線程中執行的代碼,將這段代碼用block
封裝好,然后將這個任務添加到指定的執行方式(同步執行和異步執行),等待CPU從隊列中取出任務放到對應的線程中執行。同步
sync
:一個接著一個,前一個沒有執行完,后面不能執行,不開線程。異步
async
:開啟多個新線程,任務同一時間可以一起執行(異步是多線程的代名詞)。隊列:裝載線程任務的隊形結構。(系統以先進先出的方式調度隊列中的任務執行)。在GCD中有兩種隊列:串行隊列和并發隊列。
并發隊列:線程可以同時一起進行執行。實際上是CPU在多條線程之間快速的切換。(并發功能只有在異步
dispatch_async
函數下才有效)串行隊列:線程只能依次有序的執行。
GCD總結:將任務(要在線程中執行的操作
block
)添加到隊列(自己創建或使用全局并發隊列),并且指定執行任務的方式(異步dispatch_async
,同步dispatch_sync
)
三、API
了解了基本概念,這里在介紹下本文用到幾個GCD API:
- 系統標準提供的兩個隊列
//全局隊列,也是一個并發隊列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//主隊列,在主線程中運行,因為主線程直有一個,所以這是一個串行隊列
dispatch_get_main_queue();
- 我們還可以自己創建隊列
// 從DISPATCH_QUEUE_SERIAL看出,這是串行隊列
dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
// 同理,這是一個并發隊列
dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
- 最后就是同步和異步線程了
//同步線程
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
//異步線程
dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
四、案例分析
-
案例一
NSLog(@"1");//任務1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");//任務2
});
NSLog(@"3");//任務3
分析:輸出結果:1,然后程序崩潰。
dispatch_sync
表示是一個同步線程;
dispatch_get_main_queue
表示運行在主線程中的主隊列;
任務2是同步線程的任務。
首先執行任務1,這是肯定沒問題的,只是接下來,程序遇到了同步線程,那么它會進入等待,等待任務2執行完,然后執行任務3。但這是主隊列,有任務來,當然會將任務加到隊尾,然后遵循FIFO原則執行任務。那么,現在任務2就會被加到最后,任務3排在了任務2前面,問題來了:
任務3要等任務2執行完才能執行,任務2由排在任務3后面,意味著任務2要在任務3執行完才能執行,所以他們進入了互相等待的局面。【既然這樣,那干脆就卡在這里吧】這就是死鎖。
-
案例二
NSLog(@"1"); // 任務1
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
分析:輸出結果:1,2,3。
首先執行任務1,然后遇到一個同步線程,線程會進入等待。等待任務2執行完成后才能執行任務3。從dispatch_get_global_queue
可以看出,任務2被加入到了全局并發隊列中,和主隊列并不沖突,所以在并行隊列執行完任務2之后,返回到主隊列,執行任務3。
-
案例三
dispatch_queue_t queue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1"); // 任務1
dispatch_async(queue, ^{
NSLog(@"2"); // 任務2
dispatch_sync(queue, ^{
NSLog(@"3"); // 任務3
});
NSLog(@"4"); // 任務4
});
NSLog(@"5"); // 任務5
分析:控制臺輸出1,5,2(5/2順序不確定),然后崩潰。
queue
是自己創建的串行隊列。
首先執行任務1,遇到異步線程,將【任務2,同步線程,任務4】加入到串行隊列 queue
中。因為是異步線程,所以主線程中的任務5不用等異步線程中的任務完成就可以執行(但是5和2誰先執行也不一定)。任務2執行完畢后,遇到同步線程,任務3加入到隊列queue
中,以為任務4比任務3更早加入到串行隊列queue
中,所以任務3要等任務4完成后才能執行,但是任務3所在的同步線程又會阻塞,所以任務4要等任務3完成后在執行,這就陷入了無限的等待中,造成死鎖。
-
案例四
NSLog(@"1"); // 任務1
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2"); // 任務2
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3"); // 任務3
});
NSLog(@"4"); // 任務4
});
NSLog(@"5"); // 任務5
分析:控制臺輸出結果1,5,2,3,4(5/2順序不一定)。
首先將【任務1,異步線程,任務5】加入到主隊列中,異步線程中的任務是:【任務2,同步線程,任務4】。
先執行任務1,然后將異步線程的人物加到global_queue
中,因為是異步線程,所以任務5不用等待,直接可以執行,但2和5的順序不一定。
然后再看異步線程中的任務執行順序,任務2執行完成之后,遇到同步線程,但是同步線程中的任務是加到main queue
中的,所以在主隊列中完成任務3,再回到全局隊列完成任務4。
-
案例五
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1"); // 任務1
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2"); // 任務2
});
NSLog(@"3"); // 任務3
});
NSLog(@"4"); // 任務4
while (1) {
}
NSLog(@"5"); // 任務5
分析:控制臺輸出:4,1。(1/4順序不一定)
加入到主隊列中的任務【異步線程,任務4,死循環,任務5】。
加入到異步線程中的任務【任務1,同步線程,任務3】。
任務4完成之后,程序進入死循環,主隊列阻塞。但是加入到全局隊列中的異步線程不受影響,繼續執行任務1后面的同步線程。
由于同步線程的任務2加入到主隊列,而主隊列阻塞,任務2無法執行,而任務3要等同步線程中的任務2完成才能執行,所以任務3也無法執行。
而由于死循環任務5也不會執行。