iOS14開發-多線程

理論基礎

進程與線程

進程

  • 進程是一個具備必定獨立功能的程序關於某次數據集合的一次運行活動,它是操做系統分配資源的基本單元。
  • 進程是指在系統中正在運行的一個應用程序,就是一段程序的執行過程,能夠理解爲手機上一個正在運行的 App。
  • 每一個進程之間是相互獨立的,每一個進程均運行在其專用且受保護的內存空間內,擁有獨立運行所需的所有資源。

線程

  • 程序執行的最小單元,線程是進程中的一個實體。
  • 一個進程要想執行任務,必須至少有一個線程。應用程序啓動的時候,系統會默認開啓一個線程稱之爲主線程(又稱爲main線程、UI線程)

兩者關係

  • 線程是進程的執行單元,進程的全部任務都在線程中執行。
  • 線程是 CPU 分配資源和調度的最小單位。
  • 一個程序能夠對應多個進程(多進程),一個進程中可有多個線程但至少有一個主線程。
  • 同一個進程內的線程共享進程的資源。

多線程

  • 某個時刻在單個 CPU 的核心只能執行一個線程,多線程是指 CPU 快速的在多個線程之間進行切換(調度),造成多個線程同時執行的表象。現代 CPU 都是多核,此時能夠真正同時處理多個線程。
  • 多線程的目的是爲了同時完成多項任務,經過提升系統的資源利用率來提升系統的效率。

優缺點

優勢

  • 提升程序的執行效率。
  • 提升資源利用率(CPU、內存利用率)。

缺點

  • 開啓線程須要佔用必定的內存空間,若是開啓大量的線程,會佔用大量的內存空間,下降程序的性能。
  • 線程越多,CPU 在調度時開銷就越大。
  • 程序設計更加複雜:須要解決線程之間的通訊、多線程的數據共享等問題。

線程安全

不論線程經過如何調度或線程如何交替執行,在不須要作任何干涉的狀況下,其執行結果保持一致符合預期,則稱之爲線程安全。編程

通俗解釋:在多線程中,一段代碼會被多個線程執行。假如一個線程執行了某段代碼的一部分後,被另外一個線程搶走時間片又去執行該段代碼並修改其中內容,當原線程再次回來繼續執行時裏面的內容已經被別人改動了但它並不知道,最終致使錯誤的運行結果,這種線程就是不安全的。而安全的線程是指執行一段代碼時,只要還沒有執行完,其餘線程就不能來執行這段代碼直到執行完畢。swift

串行、並行與併發

  • 串行:多個任務,執行完再執行另外一個。(吃完飯再看電視)
  • 並行:每一個線程分配給獨立的 CPU 核心,線程同時運行。(一邊吃飯一邊看電視)
  • 併發:多個線程在單個 CPU 核心運行,同一時間一個線程運行,CPU 經過調度不斷切換多個線程,造成多個線程同時執行的表象。(在餐廳吃飯,在客廳看電視)

同步與異步

同步和異步主要區別:是否開啓新的線程。安全

  • 同步執行:在當前線程中執行任務,不會開啓新線程。
  • 異步執行:能夠在新的線程中執行任務,能夠開啓新的線程,但不是必定會開啓新的線程。

多線程編程

iOS 中的多線程技術主要分爲 3 種,分別爲 Thread、GCD 和 Operation。markdown

Thread

  • 面向對象。
  • 須要手動建立線程,但不須要手動銷燬。

方式一

// Target-Action形式
let thread1 = Thread(target: self, selector: #selector(task), object: nil)
// 設置名字
thread1.name = "thread1"       
// 啓動
thread1.start()       
複製代碼

方式二

// 閉包形式
let thread2 = Thread {
    sleep(1)

    print(Thread.current)
}

thread2.name = "thread2"
thread2.start()
複製代碼

方式三

// 類方法,也有3種形式,以閉包形式爲例
// 會直接啓動線程,不須要手動調用start方法來啓動線程執行任務
Thread.detachNewThread {    
    sleep(1)
    
    print(Thread.current)  
}
複製代碼

線程狀態

線程狀態.png

線程休眠

  • sleep():休眠的時間只能爲整數。
  • Thread.sleep(forTimeInterval: ):休眠的時間能夠爲浮點數。

GCD

  • Grand Central Dispatch(宏大、中央、調度)。
  • C 語言編寫。
  • 充分利用了 CPU 多核特性,所以效率高。
  • 自動管理線程生命週期。
  • 核心概念 — 任務和隊列,將任務放進隊列便可執行。

隊列

隊列類型 功能描述
串行隊列 按照任務添加到隊列的順序執行,一次只能執行一個任務
併發隊列 同時執行一個或多個任務,但任務仍按其添加到隊列的順序啓動。
主隊列 特殊的串行隊列,會在主線程上執行任務。

DispatchQueue

  • 主隊列
// 主隊列
let main = DispatchQueue.main  
複製代碼
  • 串行隊列
// label:隊列的名稱
// 除label之外的參數都使用默認值時,返回的是串行隊列。
let serialQueue = DispatchQueue(label: "serialQueue")   
複製代碼
  • 併發隊列
// global併發隊列
let defaultGlobalDipatchQueue =  DispatchQueue.global()

// 帶qos的global併發隊列
let globalDipatchQueue = DispatchQueue.global(qos: .default)

// 建立一個併發隊列,參數attributes須要設置爲.concurrent
let concurrentDispatchQueue = DispatchQueue(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
複製代碼
參數說明
  • qos:Quality of Service,表明隊列執行的優先級(優先級越高的隊列將得到更多的計算資源),一共 6 種,默認值爲 unspecified,其他 5 種的優先級從高到低依次爲:userInteractive > userInitiated > default > utility > background。
  • attributes:包含兩個選項:

(1)concurrent:標識隊列爲併發隊列。(通常使用該選項) (2)initiallyInactive:標識隊列中的任務須要開發者手動調用activate()來觸發。若是未添加此標識,向隊列中添加的任務會自動運行。網絡

  • autoreleaseFrequency:設置負責管理任務內對象生命週期的 autorelease pool的自動釋放頻率。包含三個類型:

(1)inherit:繼承目標隊列的該屬性。 (2)workItem:跟隨每一個任務的執行週期進行自動建立和釋放。(通常使用該選項) (3)never:不會自動建立autorelease pool,須要手動管理。多線程

  • target:設置某個隊列的目標隊列,即實際將該隊列的任務放入指定隊列中運行,通常設置爲 nil。只有兩種狀況能夠顯式地設置目標隊列:

(1)初始化時指定。 (2)初始化方法中,attributes 設定爲 initiallyInactive,而後在隊列執行 activate() 以前指定。閉包

sync與async

  • sync同步方法,執行時不會當即返回,它會阻塞當前線程,等待任務執行完畢後再執行後續任務。
  • async異步方法,執行時會當即返回而後執行後續任務, 任務會在子線程中執行。
  • async 方法有多個參數,其中有 2 個比較重要:

(1)group:關聯任務的 DispatchGroup。 (2)flags:控制任務執行的環境。(該參數 sync 方法也有)併發

queue.sync {
    // 當前線程執行任務
}

queue.async {
    // 新線程執行任務
}
複製代碼

注意app

  1. 不管是併發隊列仍是串行隊列,若是是同步執行,都不會開闢新線程,只有異步執行纔會開闢新線程。
  2. 併發隊列在執行多個任務的時候,會開闢多個線程執行。而串行隊列不會,它會執行完一個再去執行另一個。

asyncAfter

在當前隊列中延遲任務的執行時間,參數爲DispatchTime,通常會在當前時間的基礎上加上一個延遲時間(以秒爲單位)。dom

func dispatchAfter() { 
    queue.asyncAfter(deadline: DispatchTime.now() + 2) {
        print("延遲2s執行")  
    }
    
    // 主隊列延遲執行
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) {
        print("主隊列延遲3s執行的任務")
    }  
}
複製代碼

concurrentPerform

  • 按指定次數異步執行任務,而且會等待指定次數的任務所有執行完畢纔會執行後面的任務,即會阻塞當前線程直到所有任務完成。
  • 默認會開啓多少個線程執行任務。
func concurrentPerform() {
    print("任務開始執行")

    DispatchQueue.concurrentPerform(iterations: 5) { index in
        for i in 0 ... 3 {
            Thread.sleep(forTimeInterval: 0.1)
            print("這是\(Thread.current)\(index)次打印:\(i)")
        }
    }

    print("任務執行完畢")
}
複製代碼

barrier

  • 用於調整併發隊列中任務之間的執行順序。
  • 同一個隊列中,barrier 以後的任務必須等其執行完纔會執行。
func barrier() {
    let queue = DispatchQueue(label: "queue001", attributes: .concurrent)

    queue.async {
        sleep(1)
        print("\(Thread.current)執行任務一")
    }

    queue.async {
        sleep(1)
        print("\(Thread.current)執行任務二")
    }

    // 任務四和五會在三以後執行
    queue.async(flags: .barrier) {
        sleep(1)
        print("\(Thread.current)執行任務三")
    }

    queue.async {
        sleep(1)
        print("\(Thread.current)執行任務四")
    }

    queue.async {
        sleep(1)
        print("\(Thread.current)執行任務五")
    }
}
複製代碼

注意:若是隊列是DispatchQueue.global(),barrier 不起做用。

DispatchGroup

  • 用於須要在多個異步任務完成之後再處理後續任務的場景。
  • notify:等待 group 中的全部任務執行完之後纔會執行的任務,該操做並不會阻塞當前線程。
  • notify 操做能夠添加屢次,也會執行屢次。
func group() {
    let group = DispatchGroup()

    queue.async(group: group) {
        print("網絡請求任務一")
    }

    queue.async(group: group) {
        print("網絡請求任務二")
    }

    queue.async(group: group) {
        print("網絡請求任務三")
    }

    // 執行完前面的任務後回到主線程執行後續任務
    group.notify(queue: DispatchQueue.main) {
        print("完成任務1、2、三, 更新UI")
    }

    queue.async {
        print("其餘任務四")
    }
    
    group.notify(queue: DispatchQueue.main) {
        print("完成任務1、2、3、四, 更新UI")
    }
}
複製代碼
  • 能夠經過enter()leave()方法顯式代表任務是否執行完成,enter()必須在leave()以前且兩者必須成對出現。
func group2() {
    let group = DispatchGroup()

    group.enter()
    queue.async(group: group) {
        print("網絡請求任務一")
        group.leave()
    }

    group.enter()
    queue.async(group: group) {
        print("網絡請求任務二")
        group.leave()
    }

    group.enter()
    queue.async(group: group) {
        print("網絡請求任務三")
        group.leave()
    }

    group.notify(queue: DispatchQueue.main) {
        print("完成任務1、2、三, 更新UI")
    }

    queue.async {
        print("其餘任務四")
    }
}
複製代碼

DispatchWorkItem

  • 任務的封裝。
  • 單獨使用時須要調用perform()方法執行任務。
func dispatchWorkItem() {    
    var value = 10
    // 初始化方法傳入一個閉包,閉包中就是須要執行的任務
    let workItem = DispatchWorkItem {
        value += 5
        print(Thread.current) // 主線程
    }
    
    // 經過perform()方法來喚起DispatchWorkItem執行任務
    workItem.perform()
    
    print(value)
}
複製代碼
  • 隊列中執行時不須要手動調用perform()方法。
let workItem = DispatchWorkItem {
    for i in 0 ... 10 {
        sleep(1)
        print(i)
        print(Thread.current) // 子線程
    }
}

DispatchQueue.global().async(execute: workItem)
複製代碼
  • cancel

(1)若是任務已經開始執行,即便取消也依然會執行。

let workItem = DispatchWorkItem {
    for i in 0 ... 10 {
        sleep(1)
        print(i)
        print(Thread.current)
    }
}

// 先執行
DispatchQueue.global().async(execute: workItem)
// 後取消
workItem.cancel()
// 查看取消狀態
print(workItem.isCancelled)
複製代碼

(2)若是任務還沒有開始執行,取消後則不會再執行。

let workItem = DispatchWorkItem {
    for i in 0 ... 10 {
        sleep(1)
        print(i)
        print(Thread.current)
    }
}
// 先取消
workItem.cancel()
// 再執行
DispatchQueue.global().async(execute: workItem)
// 查看取消狀態
print(workItem.isCancelled)
複製代碼
  • wait

(1)無參數:阻塞當前線程直到任務完成。

let workItem = DispatchWorkItem {
    for i in 0 ... 10 {
        sleep(1)
        print(i)
        print(Thread.current)
    }
}

DispatchQueue.global().async(execute: workItem)
// 等待
workItem.wait()
// 任務完成後纔會執行
print("繼續執行任務")
複製代碼

(2)timeout 參數:阻塞當前線程直到 timeout,若是任務完成 timeoutResult 爲 success,不然爲 timeOut。

let workItem = DispatchWorkItem {
    for i in 0 ... 10 {
        sleep(1)
        print(i)
        print(Thread.current)
    }
}

DispatchQueue.global().async(execute: workItem)
// 設置等待時間
let timeoutResult = workItem.wait(timeout: .now() + 3)
// 3秒內執行完任務則爲success,不然timeOut
switch timeoutResult {
case .success:
    print("success")
case .timedOut:
    print("timedOut")
}

// 3秒之後執行
print("繼續執行任務")
複製代碼
  • notify:任務完成後須要執行的操做。
let workItem = DispatchWorkItem {
    for i in 0 ... 10 {
        sleep(1)
        print(i)
        print(Thread.current)
    }
}

DispatchQueue.global().async(execute: workItem)
// 任務完成之後回到指定隊列執行任務
workItem.notify(queue: DispatchQueue.main) {
    print("任務完成")
}

print("繼續執行任務")
複製代碼

Operation

  • 基於 GCD 的封裝,更加面向對象,功能相對 GCD 也更加豐富。
  • 核心依然是任務和隊列

OperationQueue

  • 方式一
func operationUseOne() {
    // 建立OperationQueue
    let operationQueue = OperationQueue()
    
    // 添加Operation
    operationQueue.addOperation {
        sleep(1)
        print("\(Thread.current)執行任務一")
    }

    operationQueue.addOperation {
        sleep(1)
        print("\(Thread.current)執行任務二")
    }

    operationQueue.addOperation {
        sleep(1)
        print("\(Thread.current)執行任務三")
    }
}
複製代碼
  • 方式二
func operationUseTwo() {
    let operationQueue = OperationQueue()

    // BlockOperation
    let operation1 = BlockOperation {
        print("\(Thread.current)執行任務一")
        sleep(1)
    }

    let operation2 = BlockOperation {
        print("\(Thread.current)執行任務二")
        sleep(1)
    }

    let operation3 = BlockOperation {
        print("\(Thread.current)執行任務三")
        sleep(1)
    }

    // 逐個添加到OperationQueue
    // operationQueue.addOperation(operation1)
    // operationQueue.addOperation(operation2)
    // operationQueue.addOperation(operation3)

    // 一次性添加到OperationQueue
    operationQueue.addOperations([operation1, operation2, operation3], waitUntilFinished: false)

    // waitUntilFinished
    // 若是爲false,不會等任務完成再執行後續任務
    // 若是爲true,阻塞當前線程,等待任務完成後再執行後續任務
    print("\(Thread.current)執行其餘任務")
}
複製代碼
  • 主隊列
let mainQueue = OperationQueue.main

// 在沒有指定任何隊列的狀況下調用start方法啓動的BlockOperation默認會在主線程執行任務
let op = BlockOperation {
    sleep(1)
    print("\(Thread.current)執行任務一")
}

op.start()
複製代碼

maxConcurrentOperationCount

設置 OperationQueue 的最大併發數,表示的是能同時執行的 Operation 的最大數量,而不是開啓線程的最大數量

func setOperationQueue() {
      // 併發數
     operationQueue.maxConcurrentOperationCount = 2
}
複製代碼

注意:OperationQueue 沒法直接建立串行隊列(除主隊列,主隊列的最大併發數始終爲 1 ),但能夠設置最大併發數爲 1 來實現串行隊列的執行效果。

queuePriority

  • 設置 Operation 的優先級。
  • 在同一個隊列中等待調度的全部 Operation,會按照優先級排序執行,但實際執行的順序仍是依賴 CPU 的調度。
func setOperation(op:Operation){
    // 優先級
    op.queuePriority = .high
}
複製代碼

addDependency與completionBlock

  • addDependency 用於設置 Operation 之間的依賴關係。
  • 依賴操做必須在 Operation 添加到隊列以前進行。
  • 能夠跨隊列進行依賴操做。
  • completionBlock 用於設置 Operation 完成時的回調。
func dependency() {
    let operationQueue = OperationQueue()

    let operation1 = BlockOperation {
        print("\(Thread.current)執行任務一")
        sleep(1)
    }

    // 監聽Operation完成
    operation1.completionBlock = {
        print("\(Thread.current)完成任務一")
    }

    let operation2 = BlockOperation {
        print("\(Thread.current)執行任務二")
        sleep(1)
    }

    operation2.completionBlock = {
        print("\(Thread.current)完成任務二")
    }

    // 添加依賴
    // operation2在operation1執行完再執行(並非等completionBlock執行完再執行,而是BlockOperation體執行完就開始執行)
    operation2.addDependency(operation1)

    let operation3 = BlockOperation {
        print("\(Thread.current)執行任務三")
        sleep(1)
    }

    operation3.completionBlock = {
        print("\(Thread.current)完成任務三")
    }

    // operation3在operation2執行完再執行
    operation3.addDependency(operation2)

    operationQueue.addOperations([operation1, operation2, operation3], waitUntilFinished: false)

    print("\(Thread.current)執行其餘任務")
}
複製代碼

注意:串行隊列與依賴關係之間的區別?

  1. 依賴關係所處的隊列依舊是併發而非串行。
  2. 串行隊列是將任務添加到隊列之後串行執行,而依賴關係是並行執行。

barrier

相似 GCD 的 barrier。

func barrier() {
    let operationQueue = OperationQueue()

    operationQueue.addOperation {
        sleep(1)
        print("\(Thread.current)執行任務一")
    }

    operationQueue.addOperation {
        sleep(1)
        print("\(Thread.current)執行任務二")
    }

    // 任務四和五會在三以後執行
    operationQueue.addBarrierBlock {
        sleep(1)
        print("\(Thread.current)執行任務三")
    }

    operationQueue.addOperation {
        sleep(1)
        print("\(Thread.current)執行任務四")
    }

    operationQueue.addOperation {
        sleep(1)
        print("\(Thread.current)執行任務五")
    }
}
複製代碼

suspend、resume與cancel

  • suspend:掛起,OperationQueue 中尚未被 CPU 調度的 Operation 纔會被掛起,那些已經被 CPU 調度的 Operation 不會被掛起。
func suspend() {
    if operationQueue.operationCount != 0 && operationQueue.isSuspended == false {
        operationQueue.isSuspended = true
    }
}
複製代碼
  • resume:重啓,OperationQueue 中被掛起的 Operation 能夠繼續執行。
func resume() {
    if operationQueue.operationCount != 0 && operationQueue.isSuspended == true {
        operationQueue.isSuspended = false
    }
}
複製代碼
  • cancel:取消,尚未被 CPU 調度的 Operation 纔會被取消,但沒法讓其再次運行。分爲 2 種:

(1)取消單個。 (2)取消全部。

func cancel() {
    // Operation 取消 
    operation.cancel()
    // OperationQueue 取消全部
    operationQueue.cancelAllOperations()
}
複製代碼

安全性問題

多線程編程中,應該儘可能避免資源在線程之間共享,以減小線程間的相互影響。有兩個重要的概念:

  1. 臨界資源:一次只能容許一個線程使用的共享資源。
  2. 臨界區:訪問臨界資源的那段代碼。

在實際開發中,常常存在多個線程訪問同一個共享資源的狀況,那麼如何保證多線程執行結果的正確性?在 iOS 中主要提供了 2 種技術 — 鎖和信號量

  • 互斥鎖:保證在任什麼時候候,都只有一個線程訪問對象。當獲取鎖失敗時,線程會進入睡眠,等待鎖釋放時被喚醒。
  • 遞歸鎖:特殊的互斥鎖。它的特色是同一個線程能夠加鎖 N 次而不會引起死鎖。
  • 自旋鎖 :它不會引發調用者睡眠,若是自旋鎖已經被別的執行單元保持,調用者就一直循環嘗試,直到該自旋鎖的保持者已經釋放了鎖;由於不會引發調用者睡眠,因此效率高於互斥鎖。 缺點:

(1)調用者在未得到鎖的狀況下會一直運行,若是不能在很短的時間內得到鎖,會使CPU效率下降。因此自旋鎖就適用於臨界區持鎖時間很是短且CPU資源不緊張的場景。 (2)在用自旋鎖時(如遞歸調用)有可能形成死鎖。

注意:鎖操做是成對出現,有加鎖就必定有解鎖。

pthread

  • 比較底層,如今使用較少。
var mutex: pthread_mutex_t = {
    // 初始化鎖屬性
    var mutexattr = pthread_mutexattr_t()
    // 鎖屬性賦值
    pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_DEFAULT)
    // 初始化鎖
    var mutex = pthread_mutex_t()
    // pthread_mutex_init(&mutex, nil)
    // mutexattr傳nil表示default
    pthread_mutex_init(&mutex, &mutexattr)
    // 使用鎖屬性以後要釋放
    pthread_mutexattr_destroy(&mutexattr)
    // 返回鎖
    return mutex
}()

// 線程業務代碼
DispatchQueue.global().async {
    // 加鎖
    pthread_mutex_lock(&mutex)
    
    // 臨界區

    // 解鎖
    pthread_mutex_unlock(&mutex)
}
複製代碼
  • 銷燬鎖。
deinit {
    // 銷燬鎖
    pthread_mutex_destroy(&mutex)
}
複製代碼

NS系列鎖

包括NSLock、NSCondition、NSConditionLock、NSRecursiveLock,都遵照了NSLocking協議,。

  • NSLocking協議。
public protocol NSLocking {
    func lock() // 加鎖
    func unlock() // 解鎖
}
複製代碼
  • NSLock:互斥鎖。
// 初始化
let lock = NSLock() 
// 加鎖
lock.lock()

// 臨界區

// 解鎖
lock.unlock()
複製代碼
  • NSCondition:經常使用於生產者消費者模式。
// 初始化
let lock = NSCondition()
var products = [Int]()

// 消費者
func consume() {
    DispatchQueue.global().async {
        // 加鎖
        lock.lock()
        // 沒有商品掛起線程
        while products.count == 0 {
            lock.wait()
        }
        // 消費產品
        let product = products.remove(at: 0)
        print("消費產品\(product)")
        // 解鎖
        lock.unlock()
    }
}

// 生產者
func produce() {
    DispatchQueue.global().async {
        // 加鎖
        lock.lock()
        // 生產產品
        let product = Int.random(in: 0 ... 100)
        products.append(product)
        print("生產產品\(product)")
        // 喚醒消費者
        lock.signal()
        // 解鎖
        lock.unlock()
    }
}

while true {
    consume()
    sleep(1)
    produce()
}
複製代碼
  • NSConditionLock:條件鎖,對 NSCondition 的進一步封裝。
// 初始化時condition爲0
let lock = NSConditionLock(condition: 0)
var products = [Int]()

// 消費者
func consume() {
    DispatchQueue.global().async {
        // 加鎖,當參數與初始化時condition不一致時進行等待
        lock.lock(whenCondition: 1)
        // 消費產品
        let product = products.remove(at: 0)
        print("消費產品\(product)")
        // 解鎖,修改condition的值爲0
        lock.unlock(withCondition: 0)
    }
}

// 生產者
func produce() {
    DispatchQueue.global().async {
        // 加鎖,與初始化時condition一致,繼續執行
        lock.lock(whenCondition: 0)
        // 生產產品
        let product = Int.random(in: 0 ... 100)
        products.append(product)
        print("生產產品\(product)")
        // 解鎖,修改condition的值爲1
        lock.unlock(withCondition: 1)
    }
}

while true {
    consume()
    sleep(1)
    produce()
}
複製代碼
  • NSRecursiveLock:遞歸鎖。
// 初始化
let lock = NSRecursiveLock()
var count = 5

func recursive(value: Int) {
    // 加鎖(換成其餘的鎖會死鎖)
    lock.lock()
    // 大於0才繼續後面的操做
    guard value > 0 else {
        return
    }
    // 打印
    print(value)
    // 休眠
    sleep(1)
    // 遞歸次數減1
    count -= 1
    // 遞歸調用
    recursive(value: count)
    // 解鎖
    lock.unlock()
}

DispatchQueue.global().async {
    print("開始")
    recursive(value: count)
    print("結束")
}
複製代碼

objc_sync

let lock: Int = 0
// 加鎖
objc_sync_enter(lock) // 不少時候參數爲self

// 臨界區

// 解鎖
objc_sync_exit(lock)
複製代碼

OSSpinLock自旋鎖

因爲存在由於低優先級爭奪資源致使死鎖的問題,因此在 iOS 10 以後已廢棄,替換它的是 os_unfair_lock。

os_unfair_lock

一種互斥鎖,內置於os模塊。

var lock = os_unfair_lock()
// 加鎖
os_unfair_lock_lock(&lock)

// 臨界區

// 解鎖
os_unfair_lock_unlock(&lock)
複製代碼

信號量

DispatchSemaphore 是一種基於計數的信號量。它能夠設定一個閥值,多個線程競爭獲取許可信號,超過閥值後,線程申請許可信號將會被阻塞。主要用於線程之間的數據同步。

  • DispatchSemaphore(value: ):建立信號量,value 爲初始值。
  • wait:根據當前信號量的值進行判斷:

(1)若大於 0,則將信號量減 1 ,繼續執行後續任務。 (2)若小於等於 0,則阻塞當前線程,直到信號量大於 0 或者通過一個閾值時間纔會執行後續任務。

  • signal:信號量加 1。

DispatchSemaphore

// 建立信號量,初始值爲0
let semaphore = DispatchSemaphore(value: 0)

// 線程業務代碼
DispatchQueue.global().async {
    // 臨界區

    semaphore.signal()
}

semaphore.wait(timeout: .distantFuture)
複製代碼

主線程最早執行到semaphore.wait,此時信號量爲 0 且閾值時間爲.distantFuture,所以會阻塞當前主線程,直到子線程的代碼塊執行到signal()語句,將信號量加 1,此時被阻塞的主線程繼續執行,從而保證線程之間的同步。

案例

實現:模擬賣票操做,要求很多於 3 個線程同時操做。

// 初始化爲100
var number = 100
// 鎖
var lock = os_unfair_lock()

// 第一個線程
let thread1 = Thread {
    saleTicket()
}
thread1.name = "thread1"

// 第二個線程
let thread2 = Thread {
    saleTicket()
}
thread2.name = "thread2"

// 第三個線程
let thread3 = Thread {
    saleTicket()
}
thread3.name = "thread3"

// 啓動三個線程
thread1.start()
thread2.start()
thread3.start()

// 打印
func saleTicket() {
    while true {
        // 加鎖
        os_unfair_lock_lock(&lock)
        
        // 賣完
        guard number > 0 else {
            os_unfair_lock_unlock(&lock)
            print("票已賣完")
            return
        }
        
        // number減1
        number -= 1
        print("\(Thread.current.name ?? "unknown")賣出去一張票,還剩\(number)張")
        
        // 解鎖
        os_unfair_lock_unlock(&lock)
    }
}
複製代碼

UI更新問題

  • 當 App 運行之後,主線程隨之啓動。該線程須要接收用戶的交互,完成界面的更新等操做,所以必須保證它的流暢性,耗時的操做不能放在主線程中執行,不然會形成界面的卡頓甚至崩潰。
  • iOS 規定不能在子線程中更新 UI 界面,更新 UI 的操做必須在主線程中進行。若是在子線程中更新了 UI,程序在編譯時並不會報錯,但運行時會出現意料不到的結果甚至崩潰,此時控制檯和 Xcode 也會有相應的錯誤信息輸出和提示。
  • 針對 3 種不一樣的線程實現方式,回到主線程也有 3 種方式。
import UIKit

// MARK:- Thread模式
func threadMode(){    
    let thread =  Thread {  
        print("\(Thread.current)執行任務")
        // 休眠
        sleep(3)
        // 更新UI
        self.perform(#selector(self.updateUI), on: Thread.main, with: nil, waitUntilDone: false)  
    }
    
    thread.start()    
}

@objc func updateUI() { 
    self.infoLb.text = "Thread方式更新UI"
}



// MARK:- GCD模式
func gcdMode(){    
    DispatchQueue.global().async {  
        print("\(Thread.current)執行任務")
        // 休眠
        sleep(3)
        // 更新UI
        DispatchQueue.main.async {          
            self.infoLb.text = "GCD方式更新UI"
        }  
    }   
}



// MARK:- Operation模式
func operationMode(){    
    OperationQueue().addOperation {   
        print("\(Thread.current)執行任務")
        // 休眠
        sleep(3)
        // 更新UI
        OperationQueue.main.addOperation {          
            self.infoLb.text = "Operation方式更新UI"
        }
    }
}
複製代碼
相關文章
相關標籤/搜索