iOS底層原理 - 多線程原理

章前回顧

上章我們了解了鎖的一些知識(shí),線程安全需要鎖的協(xié)助。這章我們探索一下多線程原理篇;

初識(shí)

周知,了解多線程首先需要捋一下線程、進(jìn)程、同步、異步、串行、并行、死鎖等概念與關(guān)系。

多線程原理

? 線程:1、進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程的所有任務(wù)必須需要在線程中執(zhí)行;2、進(jìn)程中至少需要一條線程;3、程序啟動(dòng)默認(rèn)創(chuàng)建一條線程,創(chuàng)建的線程為主線程(UI線程);

進(jìn)程:1、每個(gè)進(jìn)程是獨(dú)立的,擁有獨(dú)立的內(nèi)存空間;2、進(jìn)程是指在系統(tǒng)正在運(yùn)行的一個(gè)程序;

線程 進(jìn)程 對(duì)比
地址空間 一個(gè)進(jìn)程內(nèi)線程地址共享 每個(gè)進(jìn)程都有一個(gè)獨(dú)立內(nèi)存地址 各有優(yōu)勢(shì)
創(chuàng)建、銷(xiāo)毀、切換 切換簡(jiǎn)單,速度快 切換復(fù)雜,速度慢 線程占優(yōu)
可靠性 一個(gè)線程掛掉會(huì)導(dǎo)致整個(gè)進(jìn)程掛 每個(gè)進(jìn)程都是獨(dú)立的,互不影響 進(jìn)程健壯
效率 占用內(nèi)存小,切換簡(jiǎn)單,CPU利用率高 占用內(nèi)存空間較大,切換復(fù)雜,CPU利用率低 線程占優(yōu)
分布 適用于多核分布式 適用多核、多機(jī)分布 進(jìn)程占優(yōu)

進(jìn)程與線程的關(guān)系

? 可以把iOS系統(tǒng)看做一個(gè)商場(chǎng),進(jìn)程(APP)則是商場(chǎng)中的店鋪,線程就是類(lèi)似店鋪雇傭的員工。

進(jìn)程之間相互獨(dú)立(奶茶店、果汁店):

? ·奶茶店看不了果汁店的賬目(訪問(wèn)不了別的進(jìn)程的內(nèi)存)

? ·果汁店用不了奶茶店的波霸 (進(jìn)程間的資源是相互獨(dú)立的)

進(jìn)程至少要一條線程:

? ·店鋪里至少需要一名員工(進(jìn)程至少有一個(gè)線程)

? ·店鋪早上開(kāi)店的員工 (進(jìn)程中的主線程)

進(jìn)程/線程崩潰

? ·奶茶店倒閉了,并不影響果汁店?duì)I業(yè)(進(jìn)程崩潰不會(huì)對(duì)其他進(jìn)程有影響)

? ·奶茶店的收銀員不干了導(dǎo)致店鋪無(wú)法正常營(yíng)業(yè)(線程崩潰進(jìn)而影響進(jìn)程癱瘓)

同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備創(chuàng)新新線程能力;

異步:在新線程中執(zhí)行任務(wù),能夠開(kāi)啟新線程;

串行:一個(gè)任務(wù)執(zhí)行完畢執(zhí)行下個(gè)任務(wù),按順序執(zhí)行;

并行:多個(gè)任務(wù)同時(shí)(并發(fā))執(zhí)行;

主隊(duì)列:特殊的串行隊(duì)列,在主線程執(zhí)行;

同步函數(shù):不會(huì)創(chuàng)建新線程,當(dāng)前線程執(zhí)行,執(zhí)行完畢后才能繼續(xù)往下執(zhí)行任務(wù),其中一條任務(wù)沒(méi)執(zhí)行完成,會(huì)卡住改函數(shù),不會(huì)往下執(zhí)行;

異步函數(shù):創(chuàng)建新線程,不要求當(dāng)前線程執(zhí)行完成,會(huì)等上個(gè)任務(wù)執(zhí)行完后再執(zhí)行;

串行隊(duì)列:任務(wù)按順序執(zhí)行,一個(gè)任務(wù)一個(gè)任務(wù)執(zhí)行;

并行隊(duì)列:多個(gè)任務(wù)同時(shí)執(zhí)行,異步函數(shù)下有效,開(kāi)啟多個(gè)線程;

串行隊(duì)列 并行隊(duì)列 主隊(duì)列
同步(sync) ·沒(méi)有開(kāi)啟新線程 ;·串行執(zhí)行任務(wù)(卡住當(dāng)前隊(duì)列) ·沒(méi)有開(kāi)啟新線程 ;·串行執(zhí)行任務(wù)(有序) ·沒(méi)有開(kāi)啟新線程 ;·串行執(zhí)行任務(wù)(死鎖)
異步(async) ·開(kāi)啟新線程 ;·串行執(zhí)行任務(wù)(無(wú)序) ·開(kāi)啟新線程 ;·并發(fā)執(zhí)行任務(wù)(無(wú)序) ·開(kāi)啟新線程 ;·串行執(zhí)行任務(wù)(無(wú)序)

死鎖

具有隊(duì)列的特點(diǎn),F(xiàn)IFO;

產(chǎn)生死鎖的情況以及解決方案:

  • 主隊(duì)列同步函數(shù),會(huì)產(chǎn)生死鎖;解決方案:主隊(duì)列(mainQueue)異步,實(shí)現(xiàn)串行執(zhí)行
  • 串行隊(duì)列使用同步函數(shù),會(huì)產(chǎn)生死鎖,即使用async函數(shù)往串行隊(duì)列中添加任務(wù);解決方案:需要按順序執(zhí)行:并行隊(duì)列加入同步函數(shù)執(zhí)行/串行隊(duì)列異步執(zhí)行;需要并發(fā)執(zhí)行:并行隊(duì)列中加入異步函數(shù)執(zhí)行

柵欄函數(shù)

? 特點(diǎn):控制任務(wù)的執(zhí)行順序。(柵欄函數(shù)之前的執(zhí)行完畢之后,執(zhí)行柵欄函數(shù),然后在執(zhí)行柵欄函數(shù)之后的)

? 在并行隊(duì)列實(shí)現(xiàn)異步函數(shù)里,順序是不固定的,加入柵欄函數(shù)可以使其按順序執(zhí)行,或者在串行隊(duì)列異步函數(shù)也可以實(shí)現(xiàn)控制執(zhí)行順序

dispatch_queue_t queue = dispatch_queue_create("com.xx.def", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    for (NSInteger i =0; i < 3; i++) {
        NSLog(@"%ld TestLine1 %@",i,[NSThread currentThread]);
    }
});

dispatch_async(queue, ^{
    for (NSInteger i =0; i < 3; i++) {
        NSLog(@"%ld TestLine2 %@",i,[NSThread currentThread]);
    }
});

dispatch_barrier_async(queue, ^{
    NSLog(@"這是個(gè)柵欄函數(shù)");
});

dispatch_async(queue, ^{
    for (NSInteger i =0; i < 3; i++) {
        NSLog(@"%ld TestLine3 %@",i,[NSThread currentThread]);
    }
});

dispatch_async(queue, ^{
    for (NSInteger i =0; i < 3; i++) {
        NSLog(@"%ld TestLine4 %@",i,[NSThread currentThread]);
    }
});
結(jié)果:
2020-04-05 00:20:27.690434+0800 Test-OC[31185:1550291] 0 TestLine2 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.690482+0800 Test-OC[31185:1550290] 0 TestLine1 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.690693+0800 Test-OC[31185:1550290] 1 TestLine1 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.690696+0800 Test-OC[31185:1550291] 1 TestLine2 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.690827+0800 Test-OC[31185:1550290] 2 TestLine1 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.690936+0800 Test-OC[31185:1550291] 2 TestLine2 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.691053+0800 Test-OC[31185:1550291] 這是個(gè)柵欄函數(shù)
2020-04-05 00:20:27.691229+0800 Test-OC[31185:1550291] 0 TestLine3 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.691950+0800 Test-OC[31185:1550290] 0 TestLine4 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.692283+0800 Test-OC[31185:1550291] 1 TestLine3 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.692670+0800 Test-OC[31185:1550290] 1 TestLine4 <NSThread: 0x6000005f2340>{number = 3, name = (null)}
2020-04-05 00:20:27.692802+0800 Test-OC[31185:1550291] 2 TestLine3 <NSThread: 0x6000005e3a00>{number = 4, name = (null)}
2020-04-05 00:20:27.692989+0800 Test-OC[31185:1550290] 2 TestLine4 <NSThread: 0x6000005f2340>{number = 3, name = (null)}

dispatch_group_notify

? 應(yīng)用類(lèi)似場(chǎng)景:用戶下載一個(gè)圖片,圖片很大,需要分成很多份進(jìn)行下載,使用GCD應(yīng)該如何實(shí)現(xiàn)?使用什么隊(duì)列?下載完后統(tǒng)一刷新UI

使用Dispatch Group追加Block到Gobal Group Queue,最后使用group_notify監(jiān)聽(tīng)執(zhí)行完所有g(shù)roup block后執(zhí)行notify 放入主線程的block;

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ /*加載圖片1 */ });
dispatch_group_async(group, queue, ^{ /*加載圖片2 */ });
dispatch_group_async(group, queue, ^{ /*加載圖片3 */ }); 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 合并圖片 });

多線程的實(shí)現(xiàn)(技術(shù)方案)

更多詳細(xì)使用可以參考:多線程詳解

  1. pthread

    優(yōu)點(diǎn)

    · C語(yǔ)言,適用Unix/Linux/Windows等系統(tǒng)

    ·可跨平臺(tái),可移植性,輕量級(jí)

    缺點(diǎn)

    ·需要手動(dòng)管理生命周期

    ·api較為復(fù)雜,適用難度較大

  2. NSThread

    優(yōu)點(diǎn)

    · 使用面向?qū)ο蠡?/p>

    ·易用,可直接操作線程對(duì)象

    缺點(diǎn)

    ·需要手動(dòng)管理生命周期

  3. GCD

    優(yōu)點(diǎn)

    · 基于C語(yǔ)言,自動(dòng)管理線程生命周期

    ·多核并行運(yùn)算的解決方案

    ·使用簡(jiǎn)易,性能優(yōu)異,經(jīng)常使用

    缺點(diǎn)

    ·在并發(fā)隊(duì)列中,順序控制相對(duì)復(fù)雜。

  4. NSOperation

    優(yōu)點(diǎn)

    · 基于GCD面向?qū)ο蟮腛C封裝,更加面向?qū)ο蠡?,自?dòng)管理線程生命周期

    ·可以添加線程依賴(lài),控制線程總數(shù),最大并發(fā)數(shù)

    ·監(jiān)聽(tīng)線程完成情況

    ·NSOPeration是一個(gè)抽象的基類(lèi),表示一個(gè)獨(dú)立的運(yùn)算單元??梢詾樽宇?lèi)提供更有效且線程安全的建立狀態(tài),優(yōu)先級(jí),線程總數(shù),依賴(lài)關(guān)系和取消等操作

    缺點(diǎn)

    ·使用面向?qū)ο?,?chuàng)建使用起來(lái)相對(duì)GCD更加復(fù)雜些,在不考慮線程控制,依賴(lài)關(guān)系下優(yōu)先選擇GCD;

多線程的生命周期

? 線程的生命周期分為5部分:新建,準(zhǔn)備就緒,運(yùn)行(執(zhí)行),阻塞,銷(xiāo)毀

? 生命周期流程圖:

[圖片上傳中...(線程生命周期聯(lián)想圖.png-fa417b-1600399385620-0)]
  • 新建:實(shí)例化新線程
  • 準(zhǔn)備就緒:向線程對(duì)象發(fā)送start消息,將線程對(duì)象加入線程池可調(diào)度狀態(tài),等待CUP調(diào)度
  • 運(yùn)行(執(zhí)行):CUP在可調(diào)度線程池中執(zhí)行線程任務(wù);當(dāng)前任務(wù)執(zhí)行完畢,CUP會(huì)進(jìn)入一個(gè)就緒狀態(tài),即進(jìn)入短暫的等待期,在此期間如果有新任務(wù)需要執(zhí)行,則執(zhí)行新任務(wù),否則該線程會(huì)銷(xiāo)毀。等有下個(gè)任務(wù)執(zhí)行,會(huì)開(kāi)啟新的線程執(zhí)行。
  • 阻塞:當(dāng)滿足某個(gè)預(yù)定條件時(shí),可以使用鎖或者休眠,來(lái)阻塞線程執(zhí)行;sleepForTimeInterval(休眠指定時(shí)長(zhǎng))sleepUntilDate(休眠到指定日期),@synchronized(object)(互斥鎖)。
  • 銷(xiāo)毀:正常銷(xiāo)毀:線程執(zhí)行完畢。非正常銷(xiāo)毀:滿足某個(gè)條件,線程內(nèi)部終止執(zhí)行或者主線程終止線程對(duì)象;

線程生命周期聯(lián)系思維導(dǎo)圖:

線程生命周期聯(lián)想圖.png

標(biāo)注:圖片等資源參考自多線程原理

線程池實(shí)現(xiàn)的原理

線程池工作原理流程圖:

線程池工作原理流程圖.png

線程與RunLoop關(guān)系

  • runLoop和線程是一一對(duì)應(yīng)的關(guān)系,一個(gè)runLoop對(duì)應(yīng)一個(gè)核心線程。核心:runLoop可以嵌套多個(gè)多個(gè)線程,但是核心只有一個(gè)。關(guān)系保存在全局字典中。
  • 在主線程中,在程序啟動(dòng)時(shí),runLoop就默認(rèn)創(chuàng)建了。
  • 在子線程中,runLoop是懶加載狀態(tài),只有使用時(shí)才會(huì)創(chuàng)建;子線程中使用定時(shí)器需注意:確保子線程runLoop的創(chuàng)建,不然定時(shí)器不會(huì)開(kāi)啟;
  • runLoop是用來(lái)管理線程的,當(dāng)線程的runLoop被開(kāi)啟,線程在執(zhí)行完畢后會(huì)進(jìn)入休眠狀態(tài),等待新任務(wù)執(zhí)行才會(huì)被喚醒
  • runLoop在主線程被開(kāi)啟時(shí)創(chuàng)建,線程結(jié)束時(shí)被銷(xiāo)毀。

線程安全介紹看官們可以移位iOS多線程安全-鎖

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

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