iOS14開發(fā)-多線程

理論基礎

進程與線程

進程

  • 進程是一個具有一定獨立功能的程序關于某次數(shù)據(jù)集合的一次運行活動,它是操作系統(tǒng)分配資源的基本單元拉背。
  • 進程是指在系統(tǒng)中正在運行的一個應用程序,就是一段程序的執(zhí)行過程,可以理解為手機上一個正在運行的 App网棍。
  • 每個進程之間是相互獨立的,每個進程均運行在其專用且受保護的內(nèi)存空間內(nèi)妇智,擁有獨立運行所需的全部資源滥玷。

線程

  • 程序執(zhí)行的最小單元氏身,線程是進程中的一個實體。
  • 一個進程要想執(zhí)行任務惑畴,必須至少有一個線程蛋欣。應用程序啟動的時候,系統(tǒng)會默認開啟一個線程稱之為主線程(又稱為main線程如贷、UI線程)陷虎。

二者關系

  • 線程是進程的執(zhí)行單元,進程的所有任務都在線程中執(zhí)行杠袱。
  • 線程是 CPU 分配資源和調(diào)度的最小單位尚猿。
  • 一個程序可以對應多個進程(多進程),一個進程中可有多個線程但至少有一個主線程霞掺。
  • 同一個進程內(nèi)的線程共享進程的資源谊路。

多線程

  • 某個時刻在單個 CPU 的核心只能執(zhí)行一個線程,多線程是指 CPU 快速的在多個線程之間進行切換(調(diào)度)菩彬,形成多個線程同時執(zhí)行的表象〔埃現(xiàn)代 CPU 都是多核,此時可以真正同時處理多個線程骗灶。
  • 多線程的目的是為了同時完成多項任務惨恭,通過提高系統(tǒng)的資源利用率來提高系統(tǒng)的效率。

優(yōu)缺點

優(yōu)點

  • 提高程序的執(zhí)行效率耙旦。
  • 提高資源利用率(CPU脱羡、內(nèi)存利用率)。

缺點

  • 開啟線程需要占用一定的內(nèi)存空間免都,如果開啟大量的線程锉罐,會占用大量的內(nèi)存空間,降低程序的性能绕娘。
  • 線程越多脓规,CPU 在調(diào)度時開銷就越大。
  • 程序設計更加復雜:需要解決線程之間的通信险领、多線程的數(shù)據(jù)共享等問題侨舆。

線程安全

不論線程通過如何調(diào)度或線程如何交替執(zhí)行,在不需要做任何干涉的情況下绢陌,其執(zhí)行結果保持一致符合預期挨下,則稱之為線程安全。

串行脐湾、并行與并發(fā)

  • 串行:多個任務臭笆,執(zhí)行完再執(zhí)行另一個。(吃完飯再看電視)
  • 并行:每個線程分配給獨立的 CPU 核心,線程同時運行耗啦。(一邊吃飯一邊看電視)
  • 并發(fā):多個線程在單個 CPU 核心運行凿菩,同一時間一個線程運行,CPU 通過調(diào)度不斷切換多個線程帜讲,形成多個線程同時執(zhí)行的表象衅谷。(在餐廳吃飯,在客廳看電視)

同步與異步

同步和異步主要區(qū)別:是否開啟新的線程似将。

  • 同步執(zhí)行:在當前線程中執(zhí)行任務获黔,不會開啟新線程。
  • 異步執(zhí)行:可以在新的線程中執(zhí)行任務在验,可以開啟新的線程玷氏,但不是一定會開啟新的線程。

多線程編程

iOS 中的多線程技術主要分為 3 種腋舌,分別為 Thread盏触、GCD 和 Operation。

Thread

  • 面向對象块饺。
  • 需要手動創(chuàng)建線程赞辩,但不需要手動銷毀。

方式一

// 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種形式辨嗽,以閉包形式為例
// 會直接啟動線程,不需要手動調(diào)用start方法來啟動線程執(zhí)行任務
Thread.detachNewThread {    
    sleep(1)
    
    print(Thread.current)  
}

線程休眠

  • sleep():休眠的時間只能為整數(shù)淮腾。
  • Thread.sleep(forTimeInterval: ):休眠的時間可以為浮點數(shù)糟需。

GCD

  • Grand Central Dispatch(宏大、中央谷朝、調(diào)度)洲押。
  • C 語言編寫。
  • 充分利用了 CPU 多核特性圆凰,因此效率高杈帐。
  • 自動管理線程生命周期。
  • 核心概念 — 任務和隊列送朱,將任務放進隊列即可執(zhí)行娘荡。

隊列

隊列類型 功能描述
串行隊列 按照任務添加到隊列的順序執(zhí)行干旁,一次只能執(zhí)行一個任務驶沼。
并發(fā)隊列 同時執(zhí)行一個或多個任務,但任務仍按其添加到隊列的順序啟動争群。
主隊列 特殊的串行隊列回怜,會在主線程上執(zhí)行任務。

DispatchQueue

  • 主隊列
// 主隊列
let main = DispatchQueue.main  
  • 串行隊列
// label:隊列的名稱
// 除label以外的參數(shù)都使用默認值時,返回的是串行隊列玉雾。
let serialQueue = DispatchQueue(label: "serialQueue")   
  • 并發(fā)隊列
// global并發(fā)隊列
let defaultGlobalDipatchQueue =  DispatchQueue.global()

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

// 創(chuàng)建一個并發(fā)隊列翔试,參數(shù)attributes需要設置為.concurrent
let concurrentDispatchQueue = DispatchQueue(label: "concurrentQueue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)

sync與async

  • sync同步方法,執(zhí)行時不會立即返回复旬,它會阻塞當前線程垦缅,等待任務執(zhí)行完畢后再執(zhí)行后續(xù)任務。
  • async異步方法驹碍,執(zhí)行時會立即返回然后執(zhí)行后續(xù)任務, 任務會在子線程中執(zhí)行壁涎。
  • async 方法有多個參數(shù),其中有 2 個比較重要:
    (1)group:關聯(lián)任務的 DispatchGroup志秃。
    (2)flags:控制任務執(zhí)行的環(huán)境怔球。(該參數(shù) sync 方法也有)
queue.sync {
    // 當前線程執(zhí)行任務
}

queue.async {
    // 新線程執(zhí)行任務
}

asyncAfter

在當前隊列中延遲任務的執(zhí)行時間,參數(shù)為DispatchTime浮还,一般會在當前時間的基礎上加上一個延遲時間(以秒為單位)竟坛。

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

concurrentPerform

  • 按指定次數(shù)異步執(zhí)行任務,并且會等待指定次數(shù)的任務全部執(zhí)行完畢才會執(zhí)行后面的任務钧舌,即會阻塞當前線程直到全部任務完成担汤。
  • 默認會開啟多少個線程執(zhí)行任務。
func concurrentPerform() {
    print("任務開始執(zhí)行")

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

    print("任務執(zhí)行完畢")
}

barrier

  • 用于調(diào)整并發(fā)隊列中任務之間的執(zhí)行順序漫试。
  • 同一個隊列中,barrier 之后的任務必須等其執(zhí)行完才會執(zhí)行碘赖。
func barrier() {
    let queue = DispatchQueue(label: "queue001", attributes: .concurrent)

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

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

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

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

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

DispatchGroup

  • 用于需要在多個異步任務完成以后再處理后續(xù)任務的場景驾荣。
  • notify:等待 group 中的所有任務執(zhí)行完以后才會執(zhí)行的任務,該操作并不會阻塞當前線程普泡。
  • notify 操作可以添加多次播掷,也會執(zhí)行多次纺涤。
func group() {
    let group = DispatchGroup()

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

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

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

    // 執(zhí)行完前面的任務后回到主線程執(zhí)行后續(xù)任務
    group.notify(queue: DispatchQueue.main) {
        print("完成任務一拒啰、二、三, 更新UI")
    }

    queue.async {
        print("其他任務四")
    }
    
    group.notify(queue: DispatchQueue.main) {
        print("完成任務一王带、二砰嘁、三件炉、四, 更新UI")
    }
}
  • 可以通過enter()leave()方法顯式表明任務是否執(zhí)行完成,enter()必須在leave()之前且二者必須成對出現(xiàn)矮湘。
func group2() {
    let group = DispatchGroup()

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

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

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

    group.notify(queue: DispatchQueue.main) {
        print("完成任務一斟冕、二、三, 更新UI")
    }

    queue.async {
        print("其他任務四")
    }
}

DispatchWorkItem

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

DispatchQueue.global().async(execute: workItem)
  • cancel

(1)如果任務已經(jīng)開始執(zhí)行秀撇,即使取消也依然會執(zhí)行超棺。

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

// 先執(zhí)行
DispatchQueue.global().async(execute: workItem)
// 后取消
workItem.cancel()
// 查看取消狀態(tài)
print(workItem.isCancelled)

(2)如果任務尚未開始執(zhí)行,取消后則不會再執(zhí)行呵燕。

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

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

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

DispatchQueue.global().async(execute: workItem)
// 等待
workItem.wait()
// 任務完成后才會執(zhí)行
print("繼續(xù)執(zhí)行任務")

(2)timeout 參數(shù):阻塞當前線程直到 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秒內(nèi)執(zhí)行完任務則為success,否則timeOut
switch timeoutResult {
case .success:
    print("success")
case .timedOut:
    print("timedOut")
}

// 3秒以后執(zhí)行
print("繼續(xù)執(zhí)行任務")
  • notify:任務完成后需要執(zhí)行的操作霍衫。
let workItem = DispatchWorkItem {
    for i in 0 ... 10 {
        sleep(1)
        print(i)
        print(Thread.current)
    }
}

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

print("繼續(xù)執(zhí)行任務")

Operation

  • 基于 GCD 的封裝候引,更加面向對象,功能相對 GCD 也更加豐富敦跌。
  • 核心依然是任務和隊列澄干。

OperationQueue

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

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

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

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

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

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

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

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

    // waitUntilFinished
    // 如果為false,不會等任務完成再執(zhí)行后續(xù)任務
    // 如果為true柠傍,阻塞當前線程麸俘,等待任務完成后再執(zhí)行后續(xù)任務
    print("\(Thread.current)執(zhí)行其他任務")
}
  • 主隊列
let mainQueue = OperationQueue.main

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

op.start()

maxConcurrentOperationCount

設置 OperationQueue 的最大并發(fā)數(shù),表示的是能同時執(zhí)行的 Operation 的最大數(shù)量惧笛,而不是開啟線程的最大數(shù)量从媚。

func setOperationQueue() {
      // 并發(fā)數(shù)
     operationQueue.maxConcurrentOperationCount = 2
}

queuePriority

  • 設置 Operation 的優(yōu)先級。
  • 在同一個隊列中等待調(diào)度的所有 Operation患整,會按照優(yōu)先級排序執(zhí)行拜效,但實際執(zhí)行的順序還是依賴 CPU 的調(diào)度。
func setOperation(op:Operation){
    // 優(yōu)先級
    op.queuePriority = .high
}

addDependency與completionBlock

  • addDependency 用于設置 Operation 之間的依賴關系各谚。
  • 依賴操作必須在 Operation 添加到隊列之前進行紧憾。
  • 可以跨隊列進行依賴操作。
  • completionBlock 用于設置 Operation 完成時的回調(diào)昌渤。
func dependency() {
    let operationQueue = OperationQueue()

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

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

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

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

    // 添加依賴
    // operation2在operation1執(zhí)行完再執(zhí)行(并不是等completionBlock執(zhí)行完再執(zhí)行赴穗,而是BlockOperation體執(zhí)行完就開始執(zhí)行)
    operation2.addDependency(operation1)

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

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

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

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

    print("\(Thread.current)執(zhí)行其他任務")
}

barrier

類似 GCD 的 barrier。

func barrier() {
    let operationQueue = OperationQueue()

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

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

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

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

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

suspend膀息、resume與cancel

  • suspend:掛起般眉,OperationQueue 中還沒有被 CPU 調(diào)度的 Operation 才會被掛起,那些已經(jīng)被 CPU 調(diào)度的 Operation 不會被掛起潜支。
func suspend() {
    if operationQueue.operationCount != 0 && operationQueue.isSuspended == false {
        operationQueue.isSuspended = true
    }
}
  • resume:重啟甸赃,OperationQueue 中被掛起的 Operation 可以繼續(xù)執(zhí)行。
func resume() {
    if operationQueue.operationCount != 0 && operationQueue.isSuspended == true {
        operationQueue.isSuspended = false
    }
}
  • cancel:取消毁腿,還沒有被 CPU 調(diào)度的 Operation 才會被取消辑奈,但無法讓其再次運行。分為 2 種:
    (1)取消單個已烤。
    (2)取消所有鸠窗。
func cancel() {
    //  Operation 取消 
    operation.cancel()
    // OperationQueue 取消所有
    operationQueue.cancelAllOperations()
}

安全性問題

多線程編程中,應該盡量避免資源在線程之間共享胯究,以減少線程間的相互影響稍计。有兩個重要的概念:

  1. 臨界資源:一次只能允許一個線程使用的共享資源。
  2. 臨界區(qū):訪問臨界資源的那段代碼裕循。

在實際開發(fā)中臣嚣,經(jīng)常存在多個線程訪問同一個共享資源的情況,那么如何保證多線程執(zhí)行結果的正確性剥哑?在 iOS 中主要提供了 2 種技術 — 鎖和信號量硅则。

  • 互斥鎖:保證在任何時候,都只有一個線程訪問對象株婴。當獲取鎖失敗時怎虫,線程會進入睡眠,等待鎖釋放時被喚醒困介。
  • 遞歸鎖:特殊的互斥鎖大审。它的特點是同一個線程可以加鎖 N 次而不會引發(fā)死鎖。
  • 自旋鎖 :它不會引起調(diào)用者睡眠座哩,如果自旋鎖已經(jīng)被別的執(zhí)行單元保持徒扶,調(diào)用者就一直循環(huán)嘗試,直到該自旋鎖的保持者已經(jīng)釋放了鎖根穷;因為不會引起調(diào)用者睡眠姜骡,所以效率高于互斥鎖。 缺點:
    (1)調(diào)用者在未獲得鎖的情況下會一直運行屿良,如果不能在很短的時間內(nèi)獲得鎖溶浴,會使CPU效率降低。所以自旋鎖就適用于臨界區(qū)持鎖時間非常短且CPU資源不緊張的場景管引。
    (2)在用自旋鎖時(如遞歸調(diào)用)有可能造成死鎖士败。

pthread

  • 比較底層,現(xiàn)在使用較少褥伴。
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
}()

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

    // 解鎖
    pthread_mutex_unlock(&mutex)
}
  • 銷毀鎖谅将。
deinit {
    // 銷毀鎖
    pthread_mutex_destroy(&mutex)
}

NS系列鎖

包括NSLock、NSCondition重慢、NSConditionLock饥臂、NSRecursiveLock,都遵守了NSLocking協(xié)議似踱,隅熙。

  • NSLocking協(xié)議稽煤。
public protocol NSLocking {
    func lock() // 加鎖
    func unlock() // 解鎖
}
  • NSLock:互斥鎖。
// 初始化
let lock = NSLock() 
// 加鎖
lock.lock()

// 臨界區(qū)

// 解鎖
lock.unlock()
  • NSCondition:常用于生產(chǎn)者消費者模式囚戚。
// 初始化
let lock = NSCondition()
var products = [Int]()

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

// 生產(chǎn)者
func produce() {
    DispatchQueue.global().async {
        // 加鎖
        lock.lock()
        // 生產(chǎn)產(chǎn)品
        let product = Int.random(in: 0 ... 100)
        products.append(product)
        print("生產(chǎn)產(chǎn)品\(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 {
        // 加鎖驰坊,當參數(shù)與初始化時condition不一致時進行等待
        lock.lock(whenCondition: 1)
        // 消費產(chǎn)品
        let product = products.remove(at: 0)
        print("消費產(chǎn)品\(product)")
        // 解鎖匾二,修改condition的值為0
        lock.unlock(withCondition: 0)
    }
}

// 生產(chǎn)者
func produce() {
    DispatchQueue.global().async {
        // 加鎖,與初始化時condition一致拳芙,繼續(xù)執(zhí)行
        lock.lock(whenCondition: 0)
        // 生產(chǎn)產(chǎn)品
        let product = Int.random(in: 0 ... 100)
        products.append(product)
        print("生產(chǎn)產(chǎn)品\(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才繼續(xù)后面的操作
    guard value > 0 else {
        return
    }
    // 打印
    print(value)
    // 休眠
    sleep(1)
    // 遞歸次數(shù)減1
    count -= 1
    // 遞歸調(diào)用
    recursive(value: count)
    // 解鎖
    lock.unlock()
}

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

objc_sync


let lock: Int = 0
// 加鎖
objc_sync_enter(lock) // 很多時候參數(shù)為self

// 臨界區(qū)

// 解鎖
objc_sync_exit(lock)

OSSpinLock自旋鎖

由于存在因為低優(yōu)先級爭奪資源導致死鎖的問題舟扎,所以在 iOS 10 之后已廢棄分飞,替換它的是 os_unfair_lock。

os_unfair_lock

一種互斥鎖睹限,內(nèi)置于os模塊浸须。

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

// 臨界區(qū)

// 解鎖
os_unfair_lock_unlock(&lock)

信號量

DispatchSemaphore 是一種基于計數(shù)的信號量。它可以設定一個閥值邦泄,多個線程競爭獲取許可信號删窒,超過閥值后,線程申請許可信號將會被阻塞顺囊。主要用于線程之間的數(shù)據(jù)同步肌索。

  • DispatchSemaphore(value: ):創(chuàng)建信號量,value 為初始值特碳。
  • wait:根據(jù)當前信號量的值進行判斷:
    (1)若大于 0诚亚,則將信號量減 1 ,繼續(xù)執(zhí)行后續(xù)任務午乓。
    (2)若小于等于 0站宗,則阻塞當前線程,直到信號量大于 0 或者經(jīng)過一個閾值時間才會執(zhí)行后續(xù)任務益愈。
  • signal:信號量加 1梢灭。

DispatchSemaphore

// 創(chuàng)建信號量,初始值為0
let semaphore = DispatchSemaphore(value: 0)

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

    semaphore.signal()
}

semaphore.wait(timeout: .distantFuture)

UI更新問題

  • 當 App 運行以后蒸其,主線程隨之啟動敏释。該線程需要接收用戶的交互,完成界面的更新等操作摸袁,因此必須保證它的流暢性钥顽,耗時的操作不能放在主線程中執(zhí)行,否則會造成界面的卡頓甚至崩潰靠汁。
  • iOS 規(guī)定不能在子線程中更新 UI 界面蜂大,更新 UI 的操作必須在主線程中進行闽铐。如果在子線程中更新了 UI,程序在編譯時并不會報錯奶浦,但運行時會出現(xiàn)意料不到的結果甚至崩潰兄墅,此時控制臺和 Xcode 也會有相應的錯誤信息輸出和提示。
  • 針對 3 種不同的線程實現(xiàn)方式财喳,回到主線程也有 3 種方式。
import UIKit

// MARK:- Thread模式
func threadMode(){    
    let thread =  Thread {  
        print("\(Thread.current)執(zhí)行任務")
        // 休眠
        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)執(zhí)行任務")
        // 休眠
        sleep(3)
        // 更新UI
        DispatchQueue.main.async {          
            self.infoLb.text = "GCD方式更新UI"
        }  
    }   
}



// MARK:- Operation模式
func operationMode(){    
    OperationQueue().addOperation {   
        print("\(Thread.current)執(zhí)行任務")
        // 休眠
        sleep(3)
        // 更新UI
        OperationQueue.main.addOperation {          
            self.infoLb.text = "Operation方式更新UI"
        }
    }
}
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末斩狱,一起剝皮案震驚了整個濱河市耳高,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌所踊,老刑警劉巖泌枪,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秕岛,居然都是意外死亡碌燕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門继薛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來修壕,“玉大人,你說我怎么就攤上這事遏考〈瑞” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵灌具,是天一觀的道長青团。 經(jīng)常有香客問我,道長咖楣,這世上最難降的妖魔是什么督笆? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮诱贿,結果婚禮上娃肿,老公的妹妹穿的比我還像新娘。我一直安慰自己珠十,他們只是感情好咸作,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宵睦,像睡著了一般记罚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上壳嚎,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天桐智,我揣著相機與錄音末早,去河邊找鬼。 笑死说庭,一個胖子當著我的面吹牛然磷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播刊驴,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼姿搜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了捆憎?” 一聲冷哼從身側響起舅柜,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎躲惰,沒想到半個月后致份,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡础拨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年氮块,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片诡宗。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡滔蝉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出塔沃,到底是詐尸還是另有隱情锰提,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布芳悲,位于F島的核電站立肘,受9級特大地震影響,放射性物質發(fā)生泄漏名扛。R本人自食惡果不足惜谅年,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肮韧。 院中可真熱鬧融蹂,春花似錦、人聲如沸弄企。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拘领。三九已至意乓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間约素,已是汗流浹背届良。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工笆凌, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人士葫。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓乞而,卻偏偏與公主長得像,于是被迫代替她去往敵國和親慢显。 傳聞我的和親對象是個殘疾皇子爪模,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容