前言
在移動(dòng)端開發(fā)中不可避免的會(huì)接觸到多線程。從用戶使用體驗(yàn)角度來講,也不可避免的會(huì)接觸到多線程的操作。
多線程基礎(chǔ)
什么是線程
線程,也被稱為輕量級(jí)進(jìn)程,是程序執(zhí)行的最小單元。一個(gè)標(biāo)準(zhǔn)的線程由線程 ID、當(dāng)前指令指針、寄存器和堆棧組成。一個(gè)進(jìn)程由一到多個(gè)線程組成,線程之間又共享程序的內(nèi)存空間和一些進(jìn)程級(jí)資源。
線程調(diào)度優(yōu)先級(jí)
當(dāng)線程數(shù)小于處理器核心數(shù)量時(shí),是真正的并發(fā),當(dāng)大于的時(shí)候,線程的并發(fā)會(huì)受到一定阻礙。這可能也是為什么 Intel 即將要推出的 i7 - 9700k 是 8 核心 8 線程的原因,而不是 i7 - 8700k 那樣擁有超線程技術(shù)的 6 核心 12 線程的 CPU。
在單核處理多線程的情況下,并發(fā)操作是模擬出來的一種狀態(tài),操作系統(tǒng)會(huì)讓這些線程輪流執(zhí)行一段時(shí)間,時(shí)間短到足以看起來這些線程是在同步執(zhí)行的。這種行為稱為線程調(diào)度。在線程調(diào)度中是有優(yōu)先級(jí)調(diào)度的,高優(yōu)先級(jí)的先執(zhí)行,低優(yōu)先級(jí)的線程通常要等到系統(tǒng)已經(jīng)沒有高優(yōu)先級(jí)的可執(zhí)行線程存在時(shí)才會(huì)開始執(zhí)行,這也是為什么 GCD 會(huì)提供 Background、utility 等優(yōu)先級(jí)選項(xiàng)。
除了用戶手動(dòng)控制線程的優(yōu)先級(jí),操作系統(tǒng)還會(huì)自動(dòng)調(diào)整線程優(yōu)先級(jí)。頻繁進(jìn)入等待狀態(tài)的線程被稱為 IO 密集型線程,很少等待,處理耗時(shí)操作長(zhǎng)時(shí)間占用時(shí)間片的線程一般稱為 CPU 密集型線程,IO 密集型線程比 CPU 密集型線程在線程優(yōu)先級(jí)的調(diào)整中,更容易獲得優(yōu)先級(jí)的提升。
在線程調(diào)度中存在一種餓死現(xiàn)象。餓死現(xiàn)象是說,這個(gè)線程的優(yōu)先級(jí)較低,而在它之前又有一個(gè)耗時(shí)的線程執(zhí)行,導(dǎo)致它無法執(zhí)行,最后餓死。為了避免這種情況,調(diào)度系統(tǒng)通常會(huì)提升那些等待時(shí)間過長(zhǎng)線程的優(yōu)先級(jí),提升到足夠讓它執(zhí)行的程度。
線程安全
數(shù)據(jù)競(jìng)爭(zhēng)
舉個(gè)例子,線程 1 有一個(gè)變量 i,并且在做 i += 1 的操作,線程 2 同時(shí)對(duì)這個(gè)變量做 i -= 1 的操作,線程 1、2 是并發(fā)執(zhí)行的,這時(shí)就會(huì)發(fā)生競(jìng)爭(zhēng)關(guān)系。
同步和鎖
同步,指在一個(gè)線程操作一個(gè)數(shù)據(jù)未結(jié)束時(shí),其他線程不得對(duì)同一個(gè)數(shù)據(jù)進(jìn)行訪問。為了避免多個(gè)線程同事讀寫一個(gè)數(shù)據(jù)而產(chǎn)生不可預(yù)知的結(jié)果,我們要將各個(gè)線程對(duì)這個(gè)數(shù)據(jù)的訪問進(jìn)行同步。
同步最常見的方法是使用鎖。每個(gè)線程在訪問數(shù)據(jù)之前會(huì)先獲取鎖,并在訪問之后釋放鎖。在鎖已經(jīng)被占用時(shí),試圖獲取鎖,線程會(huì)等待到鎖重新可用。
信號(hào)量(Semaphore)
在 iOS 中,信號(hào)量主要表現(xiàn)方式為 dispatch_semaphore_t
,最終會(huì)調(diào)用 sem_wait
方法。
和 dispatch_semaphore 相關(guān)的函數(shù)有三個(gè),創(chuàng)建信號(hào),等待信號(hào),發(fā)送信號(hào)。
信號(hào)量是允許并發(fā)訪問的,可以由一個(gè)線程獲取,另一個(gè)線程釋放。
互斥量(Mutex)
互斥量?jī)H允許一個(gè)線程訪問。互斥量和信號(hào)量不同的是,互斥量要求哪個(gè)線程獲取了,哪個(gè)線程就要負(fù)責(zé)去釋放。
在 iOS 中,pthread_mutex
可以作為互斥鎖。pthread_mutex
不是使用忙等,會(huì)阻塞線程并進(jìn)行等待。它本身擁有設(shè)置協(xié)議的功能,通過設(shè)置協(xié)議來解決優(yōu)先級(jí)反轉(zhuǎn)的問題:
pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr, int protocol)
NSLock
也是互斥鎖,只不過是用 OC 的方式暴露出來,內(nèi)部封裝了一個(gè) pthread_mutex
。在 YYKit 源碼中,ibireme 大佬頻繁使用 pthread_mutex
而不是 NSLock,是應(yīng)為 NSLock 是 OC 類,在使用時(shí)會(huì)經(jīng)過消息轉(zhuǎn)發(fā),方法調(diào)用等操作,比 pthread 略慢。
let lock = NSLock()
lock.lock()
// Todo
lock.unlock()
@synchronized(Obj)
也是一種便捷的互斥鎖創(chuàng)建方式,同事它也是一個(gè)遞歸鎖。
讀寫鎖(Read-Write Lock)
讀寫鎖,在對(duì)文件進(jìn)行操作的時(shí)候,寫操作是排他的,一旦有多個(gè)線程對(duì)同一個(gè)文件進(jìn)行寫操作,后果不可估量,但讀是可以的,多個(gè)線程讀取時(shí)沒有問題的。
- 當(dāng)讀寫鎖被一個(gè)線程以讀模式占用的時(shí)候,寫操作的其他線程會(huì)被阻塞,讀操作的其他線程還可以繼續(xù)進(jìn)行
- 當(dāng)讀寫鎖被一個(gè)線程以寫模式占用的時(shí)候,寫操作的其他線程會(huì)被阻塞,讀操作的其他線程也被阻塞
在 iOS 中,讀寫鎖主要變現(xiàn)為 pthread_rwlock_t
。
條件變量(Condition Variable)
條件變量,作用類似于一個(gè)柵欄。
- 線程可以等待條件變量,一個(gè)條件變量可以被多個(gè)線程等待。
- 線程可以喚醒條件變量,此時(shí)所有等待此變量的線程都會(huì)被喚醒。
使用條件變量,可以讓許多線程一起等待某個(gè)事件的發(fā)生,當(dāng)事件發(fā)生時(shí),所有線程可以恢復(fù)執(zhí)行。
在 iOS 中,NSCondition
表現(xiàn)為條件變量。
介紹條件變量的文章非常多,但大多都對(duì)一個(gè)一個(gè)基本問題避而不談:“為什么要用條件變量?它僅僅是控制了線程的執(zhí)行順序,用信號(hào)量或者互斥鎖能不能模擬出類似效果?”
網(wǎng)上的相關(guān)資料比較少,我簡(jiǎn)單說一下個(gè)人看法。信號(hào)量可以一定程度上替代 condition,但是互斥鎖不行。在以上給出的生產(chǎn)者-消費(fèi)者模式的代碼中, pthread_cond_wait 方法的本質(zhì)是鎖的轉(zhuǎn)移,消費(fèi)者放棄鎖,然后生產(chǎn)者獲得鎖,同理,pthread_cond_signal 則是一個(gè)鎖從生產(chǎn)者到消費(fèi)者轉(zhuǎn)移的過程。
參考鏈接:bestswifter iOS鎖的博文。
自旋鎖(Spin lock)
關(guān)于自旋鎖,可以查閱 ibirme 大佬的《不再安全的 OSSpinLock》
Thread
創(chuàng)建
Thread 創(chuàng)建有三種方式:
//第一種,手動(dò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 中,這個(gè)方法已經(jīng)不存在。@synchronized 實(shí)際上在底層是調(diào)用了 objc_sync_enter
和 objc_sync_exit
方法以及一些異常處理。所以忽略異常問題可以簡(jiǎn)單實(shí)現(xiàn)一個(gè) synchronized 方法:
func synchronized(_ lock: AnyObject, closure:() -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
經(jīng)典的售票系統(tǒng)簡(jiǎn)單模擬:
@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 對(duì)于 GCD 的封裝,但是并不局限于 GCD 的先進(jìn)先出隊(duì)列。API 更加面向?qū)ο蠡僮髌饋硎址奖恪?/p>
Operation 和 OperationQueue
Operation 相當(dāng)于 GCD 的任務(wù), OperationQueue 相當(dāng)于 GCD 的隊(duì)列。
使用 Operation 實(shí)現(xiàn)多線程的具體步驟:
- 將需要執(zhí)行的操作封裝到 Operation 對(duì)象中
- 將 Operation 添加到 OperationQueue
創(chuàng)建
一般情況下有三種使用方法:
- NSInvocaionOperation
NSInvocation 在 Swift 中已被廢除,因?yàn)樗皇穷愋桶踩?ARC 安全的。
下面是 OC 實(shí)現(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 子類需要?jiǎng)?chuàng)建一個(gè)繼承于 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 中 會(huì)自動(dòng)異步執(zhí)行 Operation 中封裝的操作,不需要再調(diào)用 Operation 的 start() 方法。
使用 addOperation(_:)
方法把 Operation 添加到隊(duì)列
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 線程間通信
下面以一個(gè)偽下載圖片的代碼來模擬 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 才會(huì)執(zhí)行。
你也可以通過 completionBlock
屬性來監(jiān)聽某個(gè)操作已經(jīng)完成。
et queue = OperationQueue()
var flag = false
let operation1 = BlockOperation {
// 模擬一個(gè)操作是否成功
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)
// 過兩秒之后控制臺(tái)才會(huì)打印 Operation1 完成和 Operation2 的執(zhí)行信息
queue.addOperation(operation1)
queue.addOperation(operation2)
取消 Operation
可以通過 Operation 的 cancel()
方法 或 Queue 的 cancelAllOperations()
來取消 Operation。
但,值得注意的是,cancel()
方法,它做的唯一做的就是將 Operation 的 isCancelled 屬性從 false 改為 true。由于它并不會(huì)真正去深入代碼將具體執(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()
// 控制臺(tái)只會(huì)輸出第二個(gè) Operation 的執(zhí)行信息。
GCD
GCD(Grand Central Dispatch) 是 Apple 推薦的方式,它將線程管理推給了系統(tǒng),用的是名為 dispatch queue 的隊(duì)列。開發(fā)者只要定義每個(gè)線程需要執(zhí)行的工作即可。所有的工作都是先進(jìn)先出,每一個(gè) block 運(yùn)轉(zhuǎn)速度極快(納秒級(jí)別)。使用場(chǎng)景主要是為了追求高效處理大量并發(fā)數(shù)據(jù),如圖片異步加載、網(wǎng)絡(luò)請(qǐng)求等。
任務(wù)和隊(duì)列
- Async:異步任務(wù)
- Sync:同步任務(wù)
DispatchQueue 是一個(gè)類似線程的概念,這里稱作對(duì)列隊(duì)列是一個(gè)FIFO數(shù)據(jù)結(jié)構(gòu),意味著先提交到隊(duì)列的任務(wù)會(huì)先開始執(zhí)行)。DispatchQueue 背后是一個(gè)由系統(tǒng)管理的線程池。
DispatchQueue 又分為串行隊(duì)列和并發(fā)隊(duì)列。
串行隊(duì)列使用同步操作容易造成死鎖,例如主線程進(jìn)行同步操作 DispatchQueue.main.sync {}
。
創(chuàng)建隊(duì)列
創(chuàng)建串行隊(duì)列
如果不設(shè)置 DispatchQueue 的 Attributes,那么默認(rèn)就會(huì)創(chuàng)建串行隊(duì)列。
- 串行隊(duì)列的同步操作:
let queue = DispatchQueue(label: "com.demo.Serial1")
// 串行隊(duì)列做同步操作, 容易造成死鎖, 不建議這樣使用
queue.sync {
print("Sync operation in a serial queue.")
}
- 串行隊(duì)列的異步操作:
let queue = DispatchQueue(label: "com.demo.Serial2")
// 串行隊(duì)列做異步操作是順序執(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ā)隊(duì)列
- 并發(fā)隊(duì)列同步操作是順序執(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ā)隊(duì)列同步操作是順序執(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ā)隊(duì)列異步操作執(zhí)行順序不定
let label = "com.demo.Concurrent2"
let attributes = DispatchQueue.Attributes.concurrent
let queue = DispatchQueue(label: label, attributes: attributes)
// 并發(fā)隊(duì)列做異步操作執(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)建主隊(duì)列和全局隊(duì)列
let mainQueue = DispatchQueue.main
let globalQueue = DispatchQueue.global()
let globalQueueWithQos = DispatchQueue.global(qos: .userInitiated)
QoS
QoS 全稱 Quality of Service
,在 Swift 中是一個(gè)結(jié)構(gòu)體,用來指定隊(duì)列或任務(wù)的優(yōu)先級(jí)。
全局隊(duì)列肯定是并發(fā)隊(duì)列。如果不指定優(yōu)先級(jí),就是默認(rèn)(default)優(yōu)先級(jí)。另外還有 background,utility,user-Initiated,unspecified,user-Interactive。下面按照優(yōu)先級(jí)順序從低到高來排列:
- Background:用來處理特別耗時(shí)的后臺(tái)操作,例如同步、備份數(shù)據(jù)。
- Utility:用來處理需要一點(diǎn)時(shí)間而又不需要立刻返回結(jié)果的操作。特別適用于異步操作,例如下載、導(dǎo)入數(shù)據(jù)。
- Default:默認(rèn)優(yōu)先級(jí)。一般來說開發(fā)者應(yīng)該指定優(yōu)先級(jí)。屬于特殊情況。
- User-Initiated:用來處理用戶觸發(fā)的、需要立刻返回結(jié)果的操作。比如打開用戶點(diǎn)擊的文件。
- User-Interactive:用來處理用戶交互的操作。一般用于主線程,如果不及時(shí)響應(yīng)就可能阻塞主線程的操作。
- Unspecified:未確定優(yōu)先級(jí),由系統(tǒng)根據(jù)不同環(huán)境推斷。比如使用過時(shí)的 API 不支持優(yōu)先級(jí),此時(shí)就可以設(shè)定為未確定優(yōu)先級(jí)。屬于特殊情況。
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ù)都完成的事件。比如,多個(gè)網(wǎng)絡(luò)請(qǐng)求同時(shí)發(fā)出去,等網(wǎng)絡(luò)請(qǐng)求都完成后 reload UI。
步驟:
- 創(chuàng)建一個(gè) DispatchGroup
- 在并發(fā)隊(duì)列中進(jìn)行異步組操作
- 通過
group.notify {}
來組合那些單個(gè)的組操作
模擬多圖下載操作:
@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ù)提交完了才會(huì)執(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)")
}
}
控制臺(tái)輸出:
由此可見,只有當(dāng) First 和 Second 執(zhí)行完畢才會(huì)執(zhí)行 Third 和 Fourth,并且 First 和 Second 執(zhí)行順序是不確定的,Third 和 Fourth 也是如此。
Semaphore
信號(hào)量,是鎖機(jī)制。
DispatchSemaphore 是傳統(tǒng)計(jì)數(shù)信號(hào)量的封裝,用來控制資源被多任務(wù)訪問的情況。
舉個(gè)例子,一共有兩個(gè)停車位,現(xiàn)在 A、B、C 都需要停車,A 和 B 先挺的情況下,C 過來了,這時(shí) C 就要等待 A 或 B 其中有一個(gè)出來,才會(huì)繼續(xù)停進(jìn)去。
注意:在串行隊(duì)列上使用信號(hào)量要注意死鎖的問題。
模擬停車操作:
let semaphore = DispatchSemaphore(value: 2)
// semaphore 在串行隊(duì)列需要注意死鎖問題
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()
}
控制臺(tái)輸出:
由此可見,第一輛車出來了,第三輛車才能進(jìn)去。