ios多線程同步異步、串行并行隊列、死鎖

隊列

概念:隊列只負責(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

參考鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個最簡單的問題,以這個作為切入點好了 在ma...
    Mr_Baymax閱讀 2,903評論 1 17
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,214評論 8 265
  • 一.概述 1.基本概念 同步與異步的概念 同步 必須等待當(dāng)前語句執(zhí)行完畢,才可以執(zhí)行下一個語句。 異步 不用等待當(dāng)...
    Jt_Self閱讀 536評論 0 1
  • 置頂保存
    秋加達瓦閱讀 377評論 0 0
  • 小紅是我的高中同學(xué),大學(xué)畢業(yè)后去了一家不錯的公司,在那她遇見了現(xiàn)在的老公。兩人結(jié)婚到現(xiàn)在,已經(jīng)有了一個一歲多的小寶...
    婷下來思考閱讀 251評論 0 0

友情鏈接更多精彩內(nèi)容