ios多線程的一些淺見

前言

在移動端開發(fā)中不可避免的會接觸到多線程。從用戶使用體驗角度來講,也不可避免的會接觸到多線程的操作。

多線程基礎(chǔ)

什么是線程

線程,也被稱為輕量級進程,是程序執(zhí)行的最小單元。一個標準的線程由線程 ID、當前指令指針、寄存器和堆棧組成。一個進程由一到多個線程組成,線程之間又共享程序的內(nèi)存空間和一些進程級資源。

線程調(diào)度優(yōu)先級

當線程數(shù)小于處理器核心數(shù)量時,是真正的并發(fā),當大于的時候,線程的并發(fā)會受到一定阻礙。這可能也是為什么 Intel 即將要推出的 i7 - 9700k 是 8 核心 8 線程的原因,而不是 i7 - 8700k 那樣擁有超線程技術(shù)的 6 核心 12 線程的 CPU。

在單核處理多線程的情況下,并發(fā)操作是模擬出來的一種狀態(tài),操作系統(tǒng)會讓這些線程輪流執(zhí)行一段時間,時間短到足以看起來這些線程是在同步執(zhí)行的。這種行為稱為線程調(diào)度。在線程調(diào)度中是有優(yōu)先級調(diào)度的,高優(yōu)先級的先執(zhí)行,低優(yōu)先級的線程通常要等到系統(tǒng)已經(jīng)沒有高優(yōu)先級的可執(zhí)行線程存在時才會開始執(zhí)行,這也是為什么 GCD 會提供 Background、utility 等優(yōu)先級選項。

除了用戶手動控制線程的優(yōu)先級,操作系統(tǒng)還會自動調(diào)整線程優(yōu)先級。頻繁進入等待狀態(tài)的線程被稱為 IO 密集型線程,很少等待,處理耗時操作長時間占用時間片的線程一般稱為 CPU 密集型線程,IO 密集型線程比 CPU 密集型線程在線程優(yōu)先級的調(diào)整中,更容易獲得優(yōu)先級的提升。

在線程調(diào)度中存在一種餓死現(xiàn)象。餓死現(xiàn)象是說,這個線程的優(yōu)先級較低,而在它之前又有一個耗時的線程執(zhí)行,導(dǎo)致它無法執(zhí)行,最后餓死。為了避免這種情況,調(diào)度系統(tǒng)通常會提升那些等待時間過長線程的優(yōu)先級,提升到足夠讓它執(zhí)行的程度。

線程安全

數(shù)據(jù)競爭

舉個例子,線程 1 有一個變量 i,并且在做 i += 1 的操作,線程 2 同時對這個變量做 i -= 1 的操作,線程 1、2 是并發(fā)執(zhí)行的,這時就會發(fā)生競爭關(guān)系。

同步和鎖

同步,指在一個線程操作一個數(shù)據(jù)未結(jié)束時,其他線程不得對同一個數(shù)據(jù)進行訪問。為了避免多個線程同事讀寫一個數(shù)據(jù)而產(chǎn)生不可預(yù)知的結(jié)果,我們要將各個線程對這個數(shù)據(jù)的訪問進行同步。

同步最常見的方法是使用鎖。每個線程在訪問數(shù)據(jù)之前會先獲取鎖,并在訪問之后釋放鎖。在鎖已經(jīng)被占用時,試圖獲取鎖,線程會等待到鎖重新可用。

信號量(Semaphore)

在 iOS 中,信號量主要表現(xiàn)方式為 dispatch_semaphore_t,最終會調(diào)用 sem_wait 方法。
和 dispatch_semaphore 相關(guān)的函數(shù)有三個,創(chuàng)建信號,等待信號,發(fā)送信號。
信號量是允許并發(fā)訪問的,可以由一個線程獲取,另一個線程釋放。

互斥量(Mutex)

互斥量僅允許一個線程訪問?;コ饬亢托盘柫坎煌氖?,互斥量要求哪個線程獲取了,哪個線程就要負責去釋放。
在 iOS 中,pthread_mutex 可以作為互斥鎖。pthread_mutex 不是使用忙等,會阻塞線程并進行等待。它本身擁有設(shè)置協(xié)議的功能,通過設(shè)置協(xié)議來解決優(yōu)先級反轉(zhuǎn)的問題:

pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol)

NSLock 也是互斥鎖,只不過是用 OC 的方式暴露出來,內(nèi)部封裝了一個 pthread_mutex。在 YYKit 源碼中,ibireme 大佬頻繁使用 pthread_mutex 而不是 NSLock,是應(yīng)為 NSLock 是 OC 類,在使用時會經(jīng)過消息轉(zhuǎn)發(fā),方法調(diào)用等操作,比 pthread 略慢。

let lock = NSLock()
lock.lock()
// Todo
lock.unlock()

@synchronized(Obj) 也是一種便捷的互斥鎖創(chuàng)建方式,同事它也是一個遞歸鎖。

讀寫鎖(Read-Write Lock)

讀寫鎖,在對文件進行操作的時候,寫操作是排他的,一旦有多個線程對同一個文件進行寫操作,后果不可估量,但讀是可以的,多個線程讀取時沒有問題的。

  1. 當讀寫鎖被一個線程以讀模式占用的時候,寫操作的其他線程會被阻塞,讀操作的其他線程還可以繼續(xù)進行
  2. 當讀寫鎖被一個線程以寫模式占用的時候,寫操作的其他線程會被阻塞,讀操作的其他線程也被阻塞

在 iOS 中,讀寫鎖主要變現(xiàn)為 pthread_rwlock_t。

條件變量(Condition Variable)

條件變量,作用類似于一個柵欄。

  1. 線程可以等待條件變量,一個條件變量可以被多個線程等待。
  2. 線程可以喚醒條件變量,此時所有等待此變量的線程都會被喚醒。

使用條件變量,可以讓許多線程一起等待某個事件的發(fā)生,當事件發(fā)生時,所有線程可以恢復(fù)執(zhí)行。

在 iOS 中,NSCondition 表現(xiàn)為條件變量。

介紹條件變量的文章非常多,但大多都對一個一個基本問題避而不談:“為什么要用條件變量?它僅僅是控制了線程的執(zhí)行順序,用信號量或者互斥鎖能不能模擬出類似效果?”

網(wǎng)上的相關(guān)資料比較少,我簡單說一下個人看法。信號量可以一定程度上替代 condition,但是互斥鎖不行。在以上給出的生產(chǎn)者-消費者模式的代碼中, pthread_cond_wait 方法的本質(zhì)是鎖的轉(zhuǎn)移,消費者放棄鎖,然后生產(chǎn)者獲得鎖,同理,pthread_cond_signal 則是一個鎖從生產(chǎn)者到消費者轉(zhuǎn)移的過程。

參考鏈接:bestswifter iOS鎖的博文。

自旋鎖(Spin lock)

關(guān)于自旋鎖,可以查閱 ibirme 大佬的《不再安全的 OSSpinLock》

Thread

創(chuàng)建

Thread 創(chuàng)建有三種方式:

//第一種,手動調(diào)用 start
// convenience init(target: Any, selector: Selector, object argument: Any?)
let thread = Thread(target: self, selector: #selector(thread1Action(_:)), object: "Thread1")
thread.name = "Background 1"
thread.start()

// 第二種,類方法
// class func detachNewThreadSelector(_ selector: Selector, toTarget target: Any, with argument: Any?)
Thread.detachNewThreadSelector(#selector(thread2Action(_:)), toTarget: self, with: "Thread2")

// 第三種 performSelector
performSelector(inBackground: #selector(thread3Action(_:)), with: "Thread3")

線程安全

在 OC 中可以添加 @synchronized() 方法方便的給線程加鎖,但是 Swift 中,這個方法已經(jīng)不存在。@synchronized 實際上在底層是調(diào)用了 objc_sync_enterobjc_sync_exit 方法以及一些異常處理。所以忽略異常問題可以簡單實現(xiàn)一個 synchronized 方法:

func synchronized(_ lock: AnyObject, closure:() -> ()) {
 objc_sync_enter(lock)
 closure()
 objc_sync_exit(lock)
}

經(jīng)典的售票系統(tǒng)簡單模擬:

@IBAction func saleTicket(_ sender: Any) {
    
    firstTicketWindow = Thread(target: self, selector: #selector(saleTicketAction), object: "Ticket Window 1")
    firstTicketWindow.name = "Ticket Window 1"
    
    secondTicketWindow = Thread(target: self, selector: #selector(saleTicketAction), object: "Ticket Window 2")
    secondTicketWindow.name = "Ticket Window 2"
    
    thirdTicketWindow = Thread(target: self, selector: #selector(saleTicketAction), object: "Ticket Window 3")
    thirdTicketWindow.name = "Ticket Window 3"
    
    firstTicketWindow.start()
    secondTicketWindow.start()
    thirdTicketWindow.start()
}

@objc func saleTicketAction() {
    
    while ticketCount > 0 {
        synchronized(self) {
            Thread.sleep(forTimeInterval: 0.1)
            if ticketCount > 0 {
                ticketCount -= 1
                print("\(Thread.current.name!) sold 1 ticket, \(self.ticketCount) remains.")
            } else {
                print("Tickets have been sold out.")
            }
        }
    }
}

線程間通信

在主線程上顯示余票:

if ticketCount > 0 {
    ticketCount -= 1
    print("\(Thread.current.name!) sold 1 ticket, \(self.ticketCount) remains.")
                    
    // 主線程顯示余票
    self.performSelector(onMainThread: #selector(showTicketNum), with: nil, waitUntilDone: true)
}

@objc func showTicketNum() {
    remainingLabel.text = "Ticket remains: \(ticketCount)"
}

Operation

Operation 是 Apple 對于 GCD 的封裝,但是并不局限于 GCD 的先進先出隊列。API 更加面向?qū)ο蠡僮髌饋硎址奖恪?/p>

Operation 和 OperationQueue

Operation 相當于 GCD 的任務(wù), OperationQueue 相當于 GCD 的隊列。
使用 Operation 實現(xiàn)多線程的具體步驟:

  • 將需要執(zhí)行的操作封裝到 Operation 對象中
  • 將 Operation 添加到 OperationQueue

創(chuàng)建

一般情況下有三種使用方法:

  • NSInvocaionOperation

NSInvocation 在 Swift 中已被廢除,因為它不是類型安全和 ARC 安全的。

下面是 OC 實現(xiàn):

- (void)testNSInvocationOperation {
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil];
    [invocationOperation start];
}

- (void)invocationOperation {
    NSLog(@"NSInvocationOperation: %@", [NSThread currentThread]);
}
  • BlockOperation
let operation = BlockOperation {
    print("An block operation without being added in a queue, the thread is: \(Thread.current)")
}
operation.start()

Block Operation 添加執(zhí)行閉包:

let operation = BlockOperation {
    print("Create a block operation in \(Thread.current).")
}
operation.addExecutionBlock {
    print("The block operation has add an execution block in \(Thread.current).")
}
operation.addExecutionBlock {
    print("The block operation has add an execution block in \(Thread.current).")
}
operation.start()
  • Operation 子類

Operation 子類需要創(chuàng)建一個繼承于 Operation 的類,需要重寫 main() 方法:

class CustomOperation: Operation {
    override func main() {
        
        // Things to do
        for _ in 0 ..< 2 {
            print("Cunstom operation in thread: \(Thread.current)")
        }
    }
}

使用:

let operation = CustomOperation()
operation.start()

OperationQueue

  • OperationQueue 直接創(chuàng)建為子線程:let queue = OperationQueue()。
  • OperationQueue 獲取主線程方法:OperationQueue.main。

將 Operation 添加到 Queue 中 會自動異步執(zhí)行 Operation 中封裝的操作,不需要再調(diào)用 Operation 的 start() 方法。

使用 addOperation(_:) 方法把 Operation 添加到隊列

let queue = OperationQueue()

let operation1 = BlockOperation {
 print("Operation 1 has beed added in a queue, in \(Thread.current).")
}

let operation2 = BlockOperation {
 print("Operation 2 has beed added in a queue, in \(Thread.current).")
}

// Operation1 和 Operation2 執(zhí)行順序是不固定的
queue.addOperation(operation1)
queue.addOperation(operation2)

使用 addOperation {} 方法添加 Operation

let queue = OperationQueue()
queue.addOperation {
    for _ in 0 ..< 2 {
        print("A queue add operation with block in \(Thread.current).")
    }
}

OperationQueue 線程間通信

下面以一個偽下載圖片的代碼來模擬 Operation 線程間通信:

let downloadQueue = OperationQueue()

indicator.startAnimating()

downloadQueue.addOperation {
    
    Thread.sleep(forTimeInterval: 1)
    
    let imageURLString = "https://clutchpoints.com/wp-content/uploads/2018/09/lebron-james.png"
    let imageURL = URL(string: imageURLString)
    let data = try? Data(contentsOf: imageURL!)
    
    guard let theData = data else {
        // 如果沒有圖片數(shù)據(jù),回到主線程停止 indicator
        OperationQueue.main.addOperation {
            self.indicator.stopAnimating()
        }
        print("Download failed.")
        return
    }
    let image = UIImage(data: theData)
    
    // 下載完圖片回到主線程更新 UI  
    OperationQueue.main.addOperation {
        if let image = image {
            self.imageView.image = image
            self.hideButton.isHidden = false
            self.imageView.isHidden = false
            self.indicator.stopAnimating()
        }
    }
}

控制 OperationQueue 最大并發(fā)數(shù)

可以通過 maxConcurrentOperationCount 來控制并發(fā)數(shù)。

let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
queue.addOperation {
 print("First operation - max concurrent number in \(Thread.current).")
}
queue.addOperation {
 print("Second operation - max concurrent number in \(Thread.current).")
}
queue.addOperation {
 print("Third operation - max concurrent number in \(Thread.current).")
}
queue.addOperation {
 print("Fourth operation - max concurrent number in \(Thread.current).")
}

依賴和完成監(jiān)聽

你可以通過 Operation 的 addDependency(_ op: Operation) 方法來添加操作間的依賴關(guān)系:
例如 operation2.addDependency(operation1) 就是說 Operation1 執(zhí)行完畢后 Operation2 才會執(zhí)行。

你也可以通過 completionBlock 屬性來監(jiān)聽某個操作已經(jīng)完成。

et queue = OperationQueue()

var flag = false
let operation1 = BlockOperation {
    // 模擬一個操作是否成功
    flag = true
    print("Operation 1 in \(Thread.current).")
    Thread.sleep(forTimeInterval: 2)
}

// 監(jiān)聽 Operation 1 是否完成
operation1.completionBlock = {
    print("Operation 1 is completed.")
}

let operation2 = BlockOperation {
    if flag {
        print("Operation 2 in \(Thread.current).")
    } else {
        print("Something went wrong.")
    }
}

operation2.addDependency(operation1)

// 過兩秒之后控制臺才會打印 Operation1 完成和 Operation2 的執(zhí)行信息
queue.addOperation(operation1)
queue.addOperation(operation2)

取消 Operation

可以通過 Operation 的 cancel() 方法 或 Queue 的 cancelAllOperations() 來取消 Operation。

但,值得注意的是,cancel() 方法,它做的唯一做的就是將 Operation 的 isCancelled 屬性從 false 改為 true。由于它并不會真正去深入代碼將具體執(zhí)行的工作暫停,所以我們必須利用 isCancelled 屬性的變化來暫停 main() 方法中的工作。

let queue = OperationQueue()
queue.addOperation {
    for i in 0 ... 100000000 {
        print("i: \(i) in \(Thread.current)")
    }
}
queue.cancelAllOperations()
queue.addOperation {
    print("Second operation in \(Thread.current)")
}

let operation = CustomOperation()
// 將 isCancelled 屬性更改為 true
operation.cancel()

// 控制臺只會輸出第二個 Operation 的執(zhí)行信息。

GCD

GCD(Grand Central Dispatch) 是 Apple 推薦的方式,它將線程管理推給了系統(tǒng),用的是名為 dispatch queue 的隊列。開發(fā)者只要定義每個線程需要執(zhí)行的工作即可。所有的工作都是先進先出,每一個 block 運轉(zhuǎn)速度極快(納秒級別)。使用場景主要是為了追求高效處理大量并發(fā)數(shù)據(jù),如圖片異步加載、網(wǎng)絡(luò)請求等。

Dispatch 在 Swift 3 中的改變

任務(wù)和隊列

  • Async:異步任務(wù)
  • Sync:同步任務(wù)

DispatchQueue 是一個類似線程的概念,這里稱作對列隊列是一個FIFO數(shù)據(jù)結(jié)構(gòu),意味著先提交到隊列的任務(wù)會先開始執(zhí)行)。DispatchQueue 背后是一個由系統(tǒng)管理的線程池。

DispatchQueue 又分為串行隊列和并發(fā)隊列。

串行隊列使用同步操作容易造成死鎖,例如主線程進行同步操作 DispatchQueue.main.sync {}。

創(chuàng)建隊列

創(chuàng)建串行隊列

如果不設(shè)置 DispatchQueue 的 Attributes,那么默認就會創(chuàng)建串行隊列。

  • 串行隊列的同步操作:
let queue = DispatchQueue(label: "com.demo.Serial1")
// 串行隊列做同步操作, 容易造成死鎖, 不建議這樣使用
queue.sync {
    print("Sync operation in a serial queue.")
}
  • 串行隊列的異步操作:
let queue = DispatchQueue(label: "com.demo.Serial2")
// 串行隊列做異步操作是順序執(zhí)行
queue.async {
    for i in 0 ..< 2 {
        print("First i: \(i)")
    }
}
queue.async {
    for i in 0 ..< 2 {
        print("Second i: \(i)")
    }
}

創(chuàng)建并發(fā)隊列

  • 并發(fā)隊列同步操作是順序執(zhí)行
let label = "com.demo.Concurrent1"
let qos = DispatchQoS.default
let attributes = DispatchQueue.Attributes.concurrent
let autoreleaseFrequency = DispatchQueue.AutoreleaseFrequency.never
let queue = DispatchQueue(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequency, target: nil)

// 并發(fā)隊列同步操作是順序執(zhí)行
queue.sync {
    for i in 0 ..< 2 {
        print("First sync i: \(i)")
    }
}
queue.sync {
    for i in 0 ..< 2 {
        print("Second sync i: \(i)")
    }
}
  • 并發(fā)隊列異步操作執(zhí)行順序不定
let label = "com.demo.Concurrent2"
let attributes = DispatchQueue.Attributes.concurrent
let queue = DispatchQueue(label: label, attributes: attributes)
        
// 并發(fā)隊列做異步操作執(zhí)行順序不固定
queue.async {
    for i in 0 ..< 2 {
        print("First async i: \(i)")
    }
}
queue.async {
    for i in 0 ..< 2 {
        print("Second async i: \(i)")
    }
}

創(chuàng)建主隊列和全局隊列

let mainQueue = DispatchQueue.main
let globalQueue = DispatchQueue.global()
let globalQueueWithQos = DispatchQueue.global(qos: .userInitiated)

QoS

QoS 全稱 Quality of Service,在 Swift 中是一個結(jié)構(gòu)體,用來指定隊列或任務(wù)的優(yōu)先級。

全局隊列肯定是并發(fā)隊列。如果不指定優(yōu)先級,就是默認(default)優(yōu)先級。另外還有 background,utility,user-Initiated,unspecified,user-Interactive。下面按照優(yōu)先級順序從低到高來排列:

  • Background:用來處理特別耗時的后臺操作,例如同步、備份數(shù)據(jù)。
  • Utility:用來處理需要一點時間而又不需要立刻返回結(jié)果的操作。特別適用于異步操作,例如下載、導(dǎo)入數(shù)據(jù)。
  • Default:默認優(yōu)先級。一般來說開發(fā)者應(yīng)該指定優(yōu)先級。屬于特殊情況。
  • User-Initiated:用來處理用戶觸發(fā)的、需要立刻返回結(jié)果的操作。比如打開用戶點擊的文件。
  • User-Interactive:用來處理用戶交互的操作。一般用于主線程,如果不及時響應(yīng)就可能阻塞主線程的操作。
  • Unspecified:未確定優(yōu)先級,由系統(tǒng)根據(jù)不同環(huán)境推斷。比如使用過時的 API 不支持優(yōu)先級,此時就可以設(shè)定為未確定優(yōu)先級。屬于特殊情況。

After 延遲

Swift 寫法如下:

override func viewDidLoad() {
    super.viewDidLoad()
    
    print("View did load.")
    let dispatchTime = DispatchTime.now() + 0.5
    DispatchQueue.main.asyncAfter(deadline: dispatchTime) {
        print("After 0.5 seconds.")
    }
}

線程間通信

模擬下載單張圖片并在 imageView 上展示:

使用 DispatchQueue.global().async {}DispatchQueue.main.async {}。

@IBAction func downloadImage(_ sender: Any) {
    indicator1.startAnimating()
    // let queue = DispatchQueue.global(qos: .default)
    DispatchQueue.global().async {
        sleep(1)
        let imageURL = URL(string: self.imageURLString1)
        let data = try? Data(contentsOf: imageURL!)
        
        guard let theData = data else {
            OperationQueue.main.addOperation {
                self.indicator1.stopAnimating()
            }
            print("Download failed.")
            return
        }
        let image = UIImage(data: theData)
        
        DispatchQueue.main.async {
            if let image = image {
                self.imageView1.image = image
                self.hideButton.isHidden = false
                self.imageView1.isHidden = false
                self.indicator1.stopAnimating()
            }
        }
    }
}

DispatchGroup

組操作,用來管理一組任務(wù)的執(zhí)行,然后監(jiān)聽任務(wù)都完成的事件。比如,多個網(wǎng)絡(luò)請求同時發(fā)出去,等網(wǎng)絡(luò)請求都完成后 reload UI。

步驟:

  1. 創(chuàng)建一個 DispatchGroup
  2. 在并發(fā)隊列中進行異步組操作
  3. 通過 group.notify {} 來組合那些單個的組操作

模擬多圖下載操作:

@IBAction func downloadImagesInGroup(_ sender: Any) {
    
    indicator1.startAnimating()
    indicator2.startAnimating()
    
    let group = DispatchGroup()
    
    let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first
    var fileURL1 = URL(fileURLWithPath: documentsPath!)
    fileURL1 = fileURL1.appendingPathComponent("LBJ1")
    fileURL1 = fileURL1.appendingPathExtension("png")
    
    var fileURL2 = URL(fileURLWithPath: documentsPath!)
    fileURL2 = fileURL2.appendingPathComponent("LBJ2")
    fileURL2 = fileURL2.appendingPathExtension("jpg")
    
    // 下載圖片1
    group.enter()
    DispatchQueue.global().async {
        
        print("Begin to download image1.")
        
        let imageURL = URL(string: self.imageURLString1)
        let data = try? Data(contentsOf: imageURL!)
        
        guard let theData = data else {
            DispatchQueue.main.async {
                self.indicator1.stopAnimating()
            }
            print("Image 1 download failed.")
            return
        }
        
        try! theData.write(to: fileURL1, options: .atomic)
        
        print("Image1 downloaded.")
        sleep(1)
        group.leave()
    }
    
    // 下載圖片2
    group.enter()
    DispatchQueue.global().async {
        
        print("Begin to download image2.")
        
        let imageURL = URL(string: self.imageURLString2)
        let data = try? Data(contentsOf: imageURL!)
        
        guard let theData = data else {
            DispatchQueue.main.async {
                self.indicator2.stopAnimating()
            }
            print("Image 2 Download failed.")
            return
        }
        
        try! theData.write(to: fileURL2, options: .atomic)
        
        sleep(1)
        print("Image2 downloaded.")
        group.leave()
    }
    
    // 在主線程展示
    group.notify(queue: .main) {
        
        let imageData1 = try? Data(contentsOf: fileURL1)
        let imageData2 = try? Data(contentsOf: fileURL2)
        
        guard let theData1 = imageData1 else {
            return
        }
        guard let theData2 = imageData2 else {
            return
        }
        
        let image1 = UIImage(data: theData1)
        let image2 = UIImage(data: theData2)
        
        self.imageView1.image = image1
        self.imageView2.image = image2
        self.imageView1.isHidden = false
        self.imageView2.isHidden = false
        self.indicator1.stopAnimating()
        self.indicator2.stopAnimating()
        self.hideButton.isHidden = false
    }
}

DispatchBarrier

柵欄函數(shù),函數(shù)之前的任務(wù)提交完了才會執(zhí)行后續(xù)的任務(wù):

let label = "com.demo.Concurrent3"
let queue = DispatchQueue(label: label, attributes: .concurrent)

queue.async {
    for i in 0 ..< 2 {
        print("First i: \(i)")
    }
}
queue.async {
    for i in 0 ..< 2 {
        print("Second i: \(i)")
    }
}

queue.async(flags: .barrier) {
    print("This is a barrier.")
}

queue.async {
    for i in 0 ..< 2 {
        print("Third i: \(i)")
    }
}
queue.async {
    for i in 0 ..< 2 {
        print("Fourth i: \(i)")
    }
}

控制臺輸出:

由此可見,只有當 First 和 Second 執(zhí)行完畢才會執(zhí)行 Third 和 Fourth,并且 First 和 Second 執(zhí)行順序是不確定的,Third 和 Fourth 也是如此。

Semaphore

信號量,是鎖機制。

DispatchSemaphore 是傳統(tǒng)計數(shù)信號量的封裝,用來控制資源被多任務(wù)訪問的情況。

舉個例子,一共有兩個停車位,現(xiàn)在 A、B、C 都需要停車,A 和 B 先挺的情況下,C 過來了,這時 C 就要等待 A 或 B 其中有一個出來,才會繼續(xù)停進去。

注意:在串行隊列上使用信號量要注意死鎖的問題。

模擬停車操作:

let semaphore = DispatchSemaphore(value: 2)

// semaphore 在串行隊列需要注意死鎖問題
let queue = DispatchQueue(label: "com.demo.Concurrent4", qos: .default, attributes: .concurrent)

queue.async {
    semaphore.wait()
    print("First car in.")
    sleep(2)
    print("First car out.")
    semaphore.signal()
}

queue.async {
    semaphore.wait()
    print("Second car in.")
    sleep(3)
    print("Second car out.")
    semaphore.signal()
}

queue.async {
    semaphore.wait()
    print("Third car in.")
    sleep(4)
    print("Third car out.")
    semaphore.signal()
}

控制臺輸出:

由此可見,第一輛車出來了,第三輛車才能進去。

最后編輯于
?著作權(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)容

  • iOS多線程實踐中,常用的就是子線程執(zhí)行耗時操作,然后回到主線程刷新UI。在iOS中每個進程啟動后都會建立一個主線...
    jackyshan閱讀 1,574評論 2 12
  • iOS多線程編程 基本知識 1. 進程(process) 進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,就是一段程序的執(zhí)...
    陵無山閱讀 6,346評論 1 14
  • 一.概述 1.基本概念 同步與異步的概念 同步 必須等待當前語句執(zhí)行完畢,才可以執(zhí)行下一個語句。 異步 不用等待當...
    Jt_Self閱讀 542評論 0 1
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 30,246評論 8 265
  • 1、阿里巴巴 馬云有一次在美國一家餐廳吃飯時,他突發(fā)奇想,找來了餐廳服務(wù)員,問他是否知道阿里巴巴這個名字。服務(wù)員回...
    GaryHost閱讀 353評論 0 0

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