iOS 底層學(xué)習(xí)22 —多線程

前言

iOS 底層第22天的學(xué)習(xí)。今天又要迎來一個新的篇章的學(xué)習(xí)。這個篇章就是多線程

多線程引入

  • 說到線程,肯定要說到另一個詞 — 進(jìn)程

進(jìn)程的基本概念

進(jìn)程: 是指在系統(tǒng)中正在運行的一個程序
每個進(jìn)程之間是獨?的,每個進(jìn)程均運?在其專?的且受保護(hù)的內(nèi)存空間內(nèi)
通過“活動監(jiān)視器”可以查看 Mac 系統(tǒng)中所開啟的進(jìn)程

線程的基本概念

線程是進(jìn)程的基本執(zhí)?單元,?個進(jìn)程的所有任務(wù)都在線程中執(zhí)?
進(jìn)程要想執(zhí)?任務(wù),必須得有線程,進(jìn)程?少要有?條線程
程序啟動會默認(rèn)開啟?條線程,這條線程被稱為主線程或 UI 線程

翻譯??

線程 是應(yīng)用程序內(nèi)實現(xiàn)多個執(zhí)行路徑的一種相對輕量級的方式。在系統(tǒng)層面,程序并排運行,系統(tǒng)根據(jù)其需求和其他程序的需求為每個程序提供執(zhí)行時間。然而,在每個程序中,存在一個或多個執(zhí)行線程,這些線程可用于同時或幾乎同時執(zhí)行不同的任務(wù)。系統(tǒng)本身實際上管理這些執(zhí)行線程,安排它們在可用核心上運行,并根據(jù)需要先發(fā)制人地中斷它們,以允許其他線程運行。

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

地址空間:同?進(jìn)程的線程共享本進(jìn)程的地址空間,?進(jìn)程之間則是獨?的地址空間。資源擁有:同?進(jìn)程內(nèi)的線程共享本進(jìn)程的資源如內(nèi)存、I/O、cpu 等,但是進(jìn)程之間的資源是獨?的。
資源擁有:同?進(jìn)程內(nèi)的線程共享本進(jìn)程的資源如內(nèi)存、I/O、cpu 等,但是進(jìn)程之間的資源是獨?的。

1: ?個進(jìn)程崩潰后,在保護(hù)模式下不會對其他進(jìn)程產(chǎn)?影響,但是?個線程崩潰整個進(jìn)程都死掉。所以多進(jìn)程要?多線程健壯。
2: 進(jìn)程切換時,消耗的資源?,效率?。所以涉及到頻繁的切換時,使?線程要好于進(jìn)程。同樣如果要求同時進(jìn)?并且?要共享某些變量的并發(fā)操作,只能?線程不能?進(jìn)程
3: 執(zhí)?過程:每個獨?的進(jìn)程有?個程序運?的??、順序執(zhí)?序列和程序??。但是線程不能獨?執(zhí)?,必須依存在應(yīng)?程序中,由應(yīng)?程序提供多個線程執(zhí)?控制。
4: 線程是處理器調(diào)度的基本單位,但是進(jìn)程不是。
5: 線程沒有地址空間,線程包含在進(jìn)程地址空間中

線程成本

  • 看下官方的這幅圖??
項目 大約成本 備注
內(nèi)核數(shù)據(jù)結(jié)構(gòu) 大約1KB 此內(nèi)存用于存儲線程數(shù)據(jù)結(jié)構(gòu)和屬性,其中大部分被分配為有線內(nèi)存,因此無法分頁到磁盤
堆??臻g 512 KB(二次線程)8 MB(OS X 主線程)1 MB(iOS主線程) 輔助線程允許的最小堆棧大小為16KB,堆棧大小必須是4KB的倍數(shù)。在線程創(chuàng)建時,此內(nèi)存的空間將放在您的進(jìn)程空間中,但在需要之前,才會創(chuàng)建與該內(nèi)存關(guān)聯(lián)的實際頁面。
創(chuàng)建時間 大約90微秒 此值反映了從創(chuàng)建線程的初始調(diào)用到線程的入口點例程開始執(zhí)行的時間。這些數(shù)字是通過分析基于英特爾的iMac上線程創(chuàng)建時生成的平均值和中位數(shù)確定的,iMac具有2GHz Core Duo處理器和運行OS X v10.5的1GB內(nèi)存。

多線程意義

  • 看一下官方文檔對多線程的解釋

翻譯??

在應(yīng)用程序中具有多個線程提供了兩個非常重要的潛在優(yōu)勢:

  • 多個線程可以提高應(yīng)用程序的感知響應(yīng)能力。
  • 多線程可以提高應(yīng)用程序在多核系統(tǒng)上的實時性能。
    如果您的應(yīng)用程序只有一個線程,則該線程必須完成所有工作。它必須響應(yīng)事件,更新應(yīng)用程序的窗口,并執(zhí)行實現(xiàn)應(yīng)用程序行為所需的所有計算。只有一個線程的問題在于它一次只能做一件事。那么,當(dāng)您的計算需要很長時間才能完成時會發(fā)生什么?當(dāng)您的代碼忙于計算所需的值時,應(yīng)用程序會停止響應(yīng)用戶事件并更新其窗口。如果這種行為持續(xù)足夠長的時間,用戶可能會認(rèn)為您的應(yīng)用程序被掛起,并試圖強制退出它。但是,如果您將自定義計算移到一個單獨的線程上,應(yīng)用程序的主線程將可以更及時地響應(yīng)用戶交互。
  • 優(yōu)點
    • 能適當(dāng)提?程序的執(zhí)?效率
    • 能適當(dāng)提?資源的利?率(CPU,內(nèi)存)
    • 線程上的任務(wù)執(zhí)?完成后,線程會?動銷毀
  • 缺點
    • 開啟線程需要占??定的內(nèi)存空間(默認(rèn)情況下,每?個線程都占512KB) 如果開啟?量的線程,會占??量的內(nèi)存空間,降低程序的性能
    • 線程越多,CPU在調(diào)?線程上的開銷就越?
    • 程序設(shè)計更加復(fù)雜,?如線程間的通信、多線程的數(shù)據(jù)共享

多線程原理

  • 多線程并非真正意義上的并發(fā),只是因為 CPU 在同一個時間點上調(diào)度線程的速度非常快,所以宏觀上就近似并行多線程了。
  • 時間?的概念:CPU在多個任務(wù)直接進(jìn)?快速的切換,這個時間間隔就是時間?
  • 單核CPU
    • 同?時間,CPU只能處理1個線程, 換?之,同?時間只有1個線程在執(zhí)?
  • 多線程同時執(zhí)?:
    • CPU快速的在多個線程之間的切換,CPU 調(diào)度線程的時間?夠快,就造成了多線程的“同時”執(zhí)?的效果
  • 如果線程數(shù)?常多
    • CPU 會在 N 個線程之間切換,消耗?量的 CPU 資源,每個線程被調(diào)度的次數(shù)會降低,線程的執(zhí)?效率降低

多線程技術(shù)方案

方案 簡介 語言 線程生命周期 使用頻率
pthread 一套通用 API ,使用難度大,跨平臺,可移值 C 手動管理 幾乎不用
NSThread 使用更加面向?qū)ο?,簡單易用,可直接操作對?/td> OC 手動管理 偶爾使用
GCD 旨在替代 NSThread 等技術(shù),充分利用設(shè)備多核 C 自動管理 經(jīng)常使用
NSOperation 底層基于 GCD,使用更加面向?qū)ο?/td> OC 自動管理 經(jīng)常使用

線程生命周期

  • 代碼??
  // 創(chuàng)建一個線程 New
  NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
  // start 
  [t start]; // 進(jìn)入 runable
  
// 對 run 的實現(xiàn)
- (void)run{
    NSLog(@"開始");
    //下面的代碼的作用是判斷線程狀態(tài),可能因為下面的延時,阻塞會帶來當(dāng)前線程的一些影響
    // sleep 模擬線程堵塞 Blocked
   [NSThread sleepForTimeInterval:3];

  for (NSInteger i = 0; i < 10; i++) {
        // 判斷線程是否被取消
        if ([NSThread currentThread].isCancelled) {
            NSLog(@"%@被取消",self.t.name);
            return;
        }
        // 開始 running 
        NSLog(@"%@ %zd", [NSThread currentThread], i);
        //內(nèi)部取消線程
        // 強制退出 - 當(dāng)某一個條件滿足,不希望線程繼續(xù)工作,直接殺死線程,退出
        // 線程 Dead
        if (i == 8) {
            // 強制退出當(dāng)前所在線程!后續(xù)的所有代碼都不會執(zhí)行
            [NSThread exit];
        }
    }
}
  • 在線程生命周期里有個東西叫 線程池
  • 那線程池是用來干什么的呢? 顧名思義 就是存放線程的嘛
  • 那線程池調(diào)度線程的具體流程是什么樣的呢? 請看??


  • 飽和策略如何處理
    ? AbortPolicy: 直接拋出RejectedExecutionExeception異常來阻?系統(tǒng)正常運?
    ? CallerRunsPolicy: 將任務(wù)回退到調(diào)?者
    ? DisOldestPolicy: 丟掉等待最久的任務(wù)
    ? DisCardPolicy: 直接丟棄任務(wù)

線程鎖

自旋鎖 vs 互斥鎖

  • 自旋鎖 : 發(fā)現(xiàn)其他線程執(zhí)行 當(dāng)前線程 詢問 - 忙等 耗費性能比較高,一般用在大端
  • 互斥鎖: 發(fā)現(xiàn)其他線程執(zhí)行 當(dāng)前線程 休眠 (就緒狀態(tài)) 一直在等打開 喚醒執(zhí)行.下面會重點講解

互斥鎖

  • 保證鎖內(nèi)的代碼,同?時間,只有?條線程能夠執(zhí)?!
  • 互斥鎖的鎖定范圍,應(yīng)該盡量?,鎖定范圍越?,效率越差!
  • 互斥鎖參數(shù)
    • 能夠加鎖的任意 NSObject 對象
    • 注意:鎖對象?定要保證所有的線程都能夠訪問
    • 如果代碼中只有?個地?需要加鎖,?多都使? self,這樣可以避免單獨再創(chuàng)建?個鎖對象

多線程知識點補充

  1. 任務(wù)執(zhí)行速度的影響因素有哪些?
  • 1.CPU (單核 or 多核)
  • 2.任務(wù)的復(fù)雜度
  • 3.優(yōu)先級的設(shè)定
  • 4.線程狀態(tài)
  1. 優(yōu)先級翻轉(zhuǎn)
    優(yōu)先級翻轉(zhuǎn)是當(dāng)一個高優(yōu)先級任務(wù)通過信號量機(jī)制訪問共享資源時,該信號量已被一低優(yōu)先級任務(wù)占有,因此造成高優(yōu)先級任務(wù)被許多具有較低優(yōu)先級任務(wù)阻塞,實時性難以得到保證。
  • 那為什么高優(yōu)先級任務(wù)會被較低優(yōu)先級任務(wù)阻塞?
    我們來解釋這下面這2個東西
IO 密集型

IO 密集型指的是系統(tǒng)的CPU性能相對硬盤、內(nèi)存要好很多,此時,系統(tǒng)運作,大部分的狀況是CPU在等I/O (硬盤/內(nèi)存) 的讀/寫操作,此時CPU Loading并不高。

CPU 密集型

CPU密集型也叫計算密集型,指的是系統(tǒng)的硬盤、內(nèi)存性能相對CPU要好很多,此時,系統(tǒng)運作大部分的狀況是 CPU Loading 100%,CPU 要讀/寫I/O(硬盤/內(nèi)存),I/O 在很短的時間就可以完成,而CPU還有許多運算要處理,CPU Loading 很高。
具體概念可參考這篇文章

小結(jié)一下:

IO 密集型 -> 線程任務(wù)頻繁等待
CPU 密集型 -> 線程任務(wù)很少等待
因此就會導(dǎo)致 IO 密集型的線程 被餓死,這時 CPU 開始進(jìn)行調(diào)度把 IO 密集型的任務(wù)的優(yōu)先級臨時的給提高上去 -> 線程被執(zhí)行的可能性提高, 就導(dǎo)致了 優(yōu)先級翻轉(zhuǎn)

優(yōu)先級因素
  1. 用戶指定
  2. 等待的頻繁的
  3. 任務(wù)長時間不執(zhí)行
  1. atomic 與 nonatomic 的區(qū)別
atomic

是原子屬性,是為多線程開發(fā)準(zhǔn)備的,是默認(rèn)屬性!
僅僅在屬性的 setter 方法中,增加了鎖(自旋鎖),能夠保證同一時間,只有一條線程對屬性進(jìn)行操作 , 同一時間 單(線程)寫多(線程)讀的線程處理技術(shù)。線程安全,需要消耗?量的資源

  • 底層源碼??
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    // ..... 
   if (!atomic) {
       oldValue = *slot;
       *slot = newValue;
   } else {
     // 添加一個自旋鎖
       spinlock_t& slotlock = PropertyLocks[slot]; 、
       slotlock.lock();
       oldValue = *slot;
       *slot = newValue;        
       slotlock.unlock();
   }
  • 從源碼可知 atomic 只是一個判斷的標(biāo)識
nonatomic

是非原子屬性
沒有鎖!性能高!?線程安全,適合內(nèi)存?的移動設(shè)

  • 小結(jié): iOS 開發(fā)建議
    所有屬性都聲明為 nonatomic
    盡量避免多線程搶奪同?塊資源
    盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減?移動客戶端的壓力

總結(jié)

  • 今天分享的多線程篇章概念性的東西比較多:
    • 什么線程,線程和進(jìn)程區(qū)別
    • 多線程的原理:它并非真正意義上的并發(fā),只是因為 CPU 在同一個時間點上調(diào)度線程的速度非常快,所以宏觀上就近似并行多線程了
    • 線程生命周期 從 new -> start -> runable -> running -> blocked -> Dead
    • 最后也補充幾個問題,擴(kuò)展了知識面。
    • 預(yù)告:下次要學(xué)習(xí)的就是iOS多線程里用的最多的 GCD 底層的探索
?著作權(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)容

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