iOS探索 多線程原理

歡迎閱讀iOS探索系列(按序閱讀食用效果更加)前端

寫在前面

多線程在iOS中有着舉足輕重的地位,不要覺得UI仔就不須要了解線程了,不光能在面試中吹噓兩句,使用得當更能保證app的質量android

1、進程、線程與隊列

1.進程的定義

  • 進程是指在系統中正在運行的一個應用程序,如微信、支付寶app都是一個進程
  • 每一個進程之間是獨立的,每一個進程均運行在齊專用的且受保護的內存

2.線程的定義

  • 線程進程的基本執行單元,一個進程的全部任務都在線程中執行
  • 進程想要執行任務,必須得有線程進程至少要有一條線程
  • 程序啓動會默認開啓一條線程,這條線程被成爲主線程UI線程

3.進程與線程的關係和區別

  • 地址空間:同一進程線程共享本進程的地址空間,而進程之間則是獨立的地址空間
  • 資源擁有:同一進程內的線程共享本進程的資源如內存、I/O、cpu等,可是進程之間的資源是獨立的
  • 一個進程崩潰後,在保護模式下不會對其餘進程產生影響,可是一個線程崩潰整個進程都死掉,因此多進程要比多線程健壯
  • 進程切換時,消耗的資源大、效率高。因此設計到頻繁的切換時,使用線程要好於進程。一樣若是要求同時進行而且又要共享某些變量的併發操做,只能用線程而不能用進程
  • 執行過程:每一個獨立的進程有一個程序運行的入口、順序執行序列和程序入口。可是線程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制
  • 線程是處理器調度的基本單位,但進程不是

可能會以爲這些理論知識很抽象,百度出來一大堆可是都很差理解,看完下面的理解就全明白了程序員

4.進程與線程的關係圖

能夠把 iOS系統想象成 商場進程則是商場中的 店鋪線程是店鋪僱傭的 員工

  • 進程之間的相互獨立
    • 奶茶店看不到果汁店的帳目(訪問不了別的進程的內存)
    • 果汁店用不了奶茶店的波霸(進程之間的資源是獨立的)
  • 進程至少要有一條線程
    • 店鋪至少要有一個員工(進程至少有一個線程)
    • 早上開店門的員工(至關於主線程)
  • 進程/線程崩潰的狀況
    • 奶茶店倒閉了並不會牽連果汁店倒閉(進程崩潰不會對其餘進程產生影響)
    • 奶茶店的收銀員不幹了會致使奶茶店沒法正常運做(線程崩潰致使進程癱瘓)

移動開發不必定是單進程處理的,android就是多進程處理的;而iOS採用沙盒機制,這也是蘋果運行可以流暢安全的一個主要緣由面試

5.隊列的定義

隊列,又稱爲佇列(queue),是先進先出(FIFO: First-In-First-Out)的線性表,在具體應用中一般用鏈表或者數組來實現。裝載線程任務的隊形結構。隊列只容許在後端(稱爲rear)進行插入操做,在前端(稱爲front)進行刪除操做。隊列的操做方式和堆棧相似,惟一的區別在於隊列只容許新數據在後端進行添加 後端

關於隊列會在下一篇文章詳細介紹數組

6.隊列和線程的關係

二者是沒有關係的,能夠這麼理解:安全

  • 隊列負責調度任務,線程執行任務微信

  • 在銀行(進程)中,有4個工做窗口(線程),而只有一條隊伍(隊列多線程

  • 窗口(線程)只負責爲排隊的人辦理業務,並不會管隊伍(隊列)是怎麼排的併發

7.線程和runloop的關係

  • runloop與線程是一一對應的——一個runloop對應一個核心的線程,爲何說是核心的,是由於runloop是能夠嵌套的,可是核心的只能有一個,他們的關係保存在一個全局的字典裏
  • runloop是來管理線程的——當線程的runloop被開啓後,線程會在執行完任務後進入休眠狀態,有了任務就會被喚醒去執行任務
  • runloop在第一次獲取時被建立,在線程結束時被銷燬
    • 對於主線程來講,runloop在程序一啓動就默認建立好了
    • 對於子線程來講,runloop是懶加載的——只有當咱們使用的時候纔會建立,因此在子線程用定時器要注意:確保子線程的runloop被建立,否則定時器不會回調

8.影響任務執行速度的因素

如下因素都會對任務的執行速度形成影響:

  • cpu的調度
  • 線程的執行速率
  • 隊列狀況
  • 任務執行的複雜度
  • 任務的優先級

2、多線程

1.多線程原理

  • 同一時間,CPU只能處理一條線程,只有一條線程在工做(執行)
  • 多線程併發(同時)執行,其實就是CPU執行快速地在多條線程之間調度(切換)

2.多線程意義

  • 優勢
    • 能適當提升程序的執行效率
    • 能適當提升資源的利用率(CPU、內存)
    • 線程上的任務執行完成後,線程會自動銷燬
  • 缺點
    • 開啓線程須要佔用必定的內存空間(默認狀況下,每個線程都佔512KB,建立線程大約須要90毫秒的建立時間)
    • 若是開啓大量的線程,會佔用大量的內存空間,下降程序的性能
    • 線程越多,CPU在調用線程上的開銷就越大
    • 程序設計更加複雜,好比線程間的通訊、多線程的數據共享

2.多線程生命週期

多線程的生命週期是:新建 - 就緒 - 運行 - 阻塞 - 死亡

  • 新建:實例化線程對象
  • 就緒:向線程對象發送start消息,線程對象被加入可調度線程池等待CPU調度。
  • 運行:CPU 負責調度可調度線程池中線程的執行。線程執行完成以前,狀態可能會在就緒和運行之間來回切換。就緒和運行之間的狀態變化由CPU負責,程序員不能干預。
  • 阻塞:當知足某個預約條件時,可使用休眠或鎖,阻塞線程執行。sleepForTimeInterval(休眠指定時長),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥鎖)。
  • 死亡:正常死亡,線程執行完畢。非正常死亡,當知足某個條件後,在線程內部停止執行/在主線程停止線程對象

4.線程池的原理

  • 線程池大小小於核心線程池大小
    • 建立線程執行任務
  • 線程池大小大於等於核心線程池大小
    1. 先判斷線程池工做隊列是否已滿
    2. 若沒滿就將任務push進隊列
    3. 若已滿時,且maximumPoolSize>corePoolSize,將建立新的線程來執行任務
    4. 反之則交給飽和策略去處理
參數名 表明意義
corePoolSize 線程池的基本大小(核心線程池大小)
maximumPool 線程池的最大大小
keepAliveTime 線程池中超過corePoolSize樹木的空閒線程的最大存活時間
unit keepAliveTime參數的時間單位
workQueue 任務阻塞隊列
threadFactory 新建線程的工廠
handler 當提交的任務數超過maxmumPoolSize與workQueue之和時,
任務會交給RejectedExecutionHandler來處理

飽和策略有以下四個:

  • AbortPolicy直接拋出RejectedExecutionExeception異常來阻止系統正常運行
  • CallerRunsPolicy將任務回退到調用者
  • DisOldestPolicy丟掉等待最久的任務
  • DisCardPolicy直接丟棄任務

4.多線程實現方案

技術方案 簡介 語言 線程生命週期 使用評率
pthread 一套通用的多線程API
適用於Unix/Linux/Windows等系統
跨平臺/可移植
使用難度大
C 程序員管理 幾乎不用
NSThread 使用更加面向對象
簡單易用,可直接操做線程對象
OC 程序員管理 偶爾使用
GCD 旨在替代NSThread等線程技術
充分利用設備的多核
C 自動管理 常用
NSOperation 基於GCD(底層是GCD)
比GCD多了一些更簡單實用的功能
使用更加面向對象
OC 自動管理 常用

5.GCD和NSOperation的區別

  • GCD僅僅支持FIFO隊列,不支持異步操做之間的依賴關係設置。而NSOperation中的隊列能夠被從新設置優先級,從而實現不一樣操做的執行順序調整
  • NSOperation支持KVO,能夠觀察任務的執行狀態
  • GCD更接近底層,GCD在追求性能的底層操做來講,是速度最快的
  • 從異步操做之間的事務性,順序行,依賴關係。GCD須要本身寫更多的代碼來實現,而NSOperation已經內建了這些支持
  • 若是異步操做的過程須要更多的被交互和UI呈現出來,NSOperation更好;底層代碼中,任務之間不太互相依賴,而須要更高的併發能力,GCD則更有優點

6.線程間通信

  • 直接消息傳遞: 經過performSelector的一系列方法,能夠實現由某一線程指定在另外的線程上執行任務。由於任務的執行上下文是目標線程,這種方式發送的消息將會自動的被序列化
  • 全局變量、共享內存塊和對象: 在兩個線程之間傳遞信息的另外一種簡單方法是使用全局變量,共享對象或共享內存塊。儘管共享變量既快速又簡單,可是它們比直接消息傳遞更脆弱。必須使用鎖或其餘同步機制仔細保護共享變量,以確保代碼的正確性。 不然可能會致使競爭情況,數據損壞或崩潰。
  • 條件執行: 條件是一種同步工具,可用於控制線程什麼時候執行代碼的特定部分。您能夠將條件視爲關守,讓線程僅在知足指定條件時運行。
  • Runloop sources: 一個自定義的 Runloop source 配置可讓一個線程上收到特定的應用程序消息。因爲 Runloop source 是事件驅動的,所以在無事可作時,線程會自動進入睡眠狀態,從而提升了線程的效率
  • Ports and sockets:基於端口的通訊是在兩個線程之間進行通訊的一種更爲複雜的方法,但它也是一種很是可靠的技術。更重要的是,端口和套接字可用於與外部實體(例如其餘進程和服務)進行通訊。爲了提升效率,使用 Runloop source 來實現端口,所以當端口上沒有數據等待時,線程將進入睡眠狀態
  • 消息隊列: 傳統的多處理服務定義了先進先出(FIFO)隊列抽象,用於管理傳入和傳出數據。儘管消息隊列既簡單又方便,可是它們不如其餘一些通訊技術高效
  • Cocoa 分佈式對象: 分佈式對象是一種 Cocoa 技術,可提供基於端口的通訊的高級實現。儘管能夠將這種技術用於線程間通訊,可是強烈建議不要這樣作,由於它會產生大量開銷。分佈式對象更適合與其餘進程進行通訊,儘管在這些進程之間進行事務的開銷也很高

寫在後面

本文初步介紹了多線程,下篇文章講解多線程中的主角——GCD

相關文章
相關標籤/搜索