概念
多線程編程 先來看一下下面的代碼。
int main() {
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
上面的代碼最終會轉化為機器能夠識別的指令(二進制代碼)。然后這些命令在 CPU 中一句一句的執行。由于一個 CPU 一次只能執行一個命令,不能執行某處分開的并列的兩個命令,因此通過 CPU 執行的 CPU 命令就好像一條無分叉的大道,其執行不會出現分歧。這里所說的“一個 CPU 執行的 CPU 命令為一條無分叉路徑”即為“線程”。
在單個 CPU的情況下, 使用多線程的程序可以在某個線程和其他線程之間反復的進行上下文切換,因此看上去好像一個 CPU 可以并列執行多個線程。在具有多個 CPU 的情況下,就是真的提供了多個 CPU 并行執行多個線程的任務了。
當然多線程編程也會帶來一定的問題:
- 多個線程更新相同的資源導致數據的不一致(數據競爭)
- 多個線程相互等待導致的死鎖問題
- 開啟多個線程會消耗大量內存
在 iOS 程序啟動時,最先執行的線程,就是主線程,主線程用來描繪用戶界面、處理觸摸屏幕的事件等。 如果在這個線程中進行長時間的操作,就會妨礙主線程的執行,妨礙主線程中的 RunLoop 更新,從而導致不能更新用戶界面、應用畫面長時間停滯等問題。
GCD (Grand Central Dispatch) 通過 GCD 開發者只需要定義想要執行的任務并追加到適當的 Dispatch Queue 中, GCD 就能生成必要的線程并計劃執行任務。它將線程管理作為系統的一部分來實現的,因此可統一管理,這樣就相對來說更加有效率。
GCD 簡單使用
重要知識點
- async/sync
- sync 同步任務,會堵塞當前線程等待任務執行完
- async 異步任務,不會堵塞當前線程,只要有任務就會繼續執行
- Dispatch Queue(列隊)
- Serial Dispatch Queue (等待前一個任務完成后才能繼續執行下一個任務)
- Global Dispatch Queue(系統系統的全局的同步列隊)
- Concurrent Dispatch Queue(不必等待前一個任務執行完成也可以繼續執行下一個任務)
- Main Dispatch Queue(系統主線程,主要繪制用戶界面、處理屏幕事件)
- Serial Dispatch Queue (等待前一個任務完成后才能繼續執行下一個任務)
使用方法: 將指定的任務追加到適當的 Dispatch Queue 中
//Swift
DispatchQueue.global().async {
/**
*想要執行的任務
*/
}
//Objective-C
dispatch_async(queue, ^{
/*
*想要執行的任務
*/
})
創建列隊
- Objective-C
//Serial Dispatch Queue
dispatch_queue_t serailDispatchQueue = dispatch_queue_create("com.cbreno.gcd.objective.serial, NULL);
dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.cbreno.gcd.objectivec.serial", DISPATCH_QUEUE_SERIAL);
//Concurrent Dispatch Queue
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.cbreno.gcd.objectivec.concurrent", DISPATCH_QUEUE_CONCURRENT);
- Swift
//Serial Dispatch Queue
let serialQueue = DispatchQueue(lable: "com.cbreno.gcd.swift.serial")
//Concurrent Dispatch Queue
let concurrentQueue = DispatchQueue(lable: "com.cbreno.gcd.swift.concurrent", attributes: .concurrent)
- Serial Dispatch Queue 一旦生成并且追加處理,系統對于一個 Serial Dispatch Queue 就只生成并使用一個線程。如果過多使用多線程,會消耗大量內存,引起大量的上下文切換,大幅度江都一同的響應性能。
- 為了避免多線程更新相同資源導致數據競爭問題,使用 Serial Dispatch Queue。
- Dispatch Queue 的名稱推薦使用應用程序ID的逆序全程域名,這么做的好處是在Xcode 和 Instruments 調試的時候很方便觀察。另外應用奔潰時產生的 CrashLog 中很容易找到對應線程。
Main Dispatch Queue/Global Dispatch Queue
- Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//Swift中這是一個 DispatchQueue 類型的變量
let mainQueue = DispatchQueue.main
Main Dispatch Queue 是主線程中執行的 Dispatch Queue ,是一個 Serial Dispatch Queue。追加到 Main Dispatch Queue 的處理在主線程的 RunLoop 中執行。主線程主要處理用戶界面的渲染、觸摸事件等, 所以需要長時間的操作,最好在別的線程中執行,執行完后再在主線程中刷新UI等操作。
- Global Dispatch Queue
//Objective-C
//DISPATCH_QUEUE_PRIORITY_HIGH
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//DISPATCH_QUEUE_PRIORITY_LOW
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//DISPATCH_QUEUE_PRIORITY_BACKGROUND
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//Swift
//userInitiated
let globalQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated)
//default
let globalQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
//utility
let globalQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.utility)
//background
let globalQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.background)
//在Swift中優先級是一個 enum 類型
public enum QoSClass {
case background
case utility
case `default`
case userInitiated
case userInteractive
case unspecified
public init?(rawValue: qos_class_t)
public var rawValue: qos_class_t { get }
}
- 我們可以直接使用系統提供的這兩個Dispatch Queue 幫助我們完成一般操作,像下面一樣:
//Objective-C
//Global Dispatch Queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) ^{
//處理長時間的操作
.....
//使用Main Dispatch Queue 在主線程中操作
dispatch_async(dispatch_get_main_queue(), ^{
//只能在主線程中執行的操作
});
});
//Swift
//Global Dispatch Queue
DispatchQueue.global().async {
//處理長時間的操作
.....
//使用Main Dispatch Queue 在主線程中操作
DispatchQueue.main.async {
//只能在主線程中執行的操作
}
}
- Dispatch After
當需要延遲執行的情況就要用到 Dispatch After,在指定的時間后將指定的 Block 追加到指定的Dispatch Queue 中。
//Objective-C
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
..........
});
-
第一個參數是指定需要延遲的時間,是dispatch_time_t 類型的值。
dispatch_time 方法的第一個參數是 dispatch_time 類型的有兩種值:
//A somewhat abstract representation of time; where zero means "now" and
#define DISPATCH_TIME_NOW (0ull)
//DISPATCH_TIME_FOREVER means "infinity" and every value in between is an
#define DISPATCH_TIME_FOREVER (~0ull)
dispatch_time 方法的第二個參數有以下幾種類型:
#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
ull
表示 unsigned long long
。
第二個參數是需要追加處理的 Dispatch Queue。
第三個參數是需要追加的 Block。
//Swift
let delay = DispatchTime.now() + DispatchTimeInterval.seconds(60)
//let delay = DispatchTime.now() + 3.0
DispatchQueue.main.asyncAfter(deadline: delay) {
//需要延遲執行的任務
}
- 在定義延遲時間的時候可以直接在 DispatchTime.now() 方法后面加上需要的延遲的時間,是因為蘋果定義了
+
方法,使得它可以直接操作。
//Swift
public func +(time: DispatchTime, seconds: Double) -> DispatchTime
當然還定義了一些其他的方法:
//Swift
public func +(time: DispatchWallTime, seconds: Double) -> DispatchWallTime
public func +(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime
public func +(time: DispatchWallTime, interval: DispatchTimeInterval) -> DispatchWallTime
public func -(time: DispatchTime, interval: DispatchTimeInterval) -> DispatchTime
public func -(time: DispatchTime, seconds: Double) -> DispatchTime
public func -(time: DispatchWallTime, interval: DispatchTimeInterval) -> DispatchWallTime
public func -(time: DispatchWallTime, seconds: Double) -> DispatchWallTime
public func <(a: DispatchTime, b: DispatchTime) -> Bool
public func <(a: DispatchWallTime, b: DispatchWallTime) -> Bool
public func ==(a: DispatchTime, b: DispatchTime) -> Bool
public func ==(a: DispatchQoS, b: DispatchQoS) -> Bool
public func ==(a: DispatchWallTime, b: DispatchWallTime) -> Bool
Dispatch Group
如果有這樣的需求,需要等待所有的線程中的任務都執行完成后,做統一的處理。通常為多個網絡請求操作,需要等待所有的網絡請求完成后,解析數據,統一刷新UI。這時候我們就可以通過 Dispatch queue 來完成我們的操作。當然在使用一個 Serial Dispatch Queue 的時候只要將所有的任務追加到其中并且在最后處理結果即可實現。但是在使用 Concurrent Dispatch Queue 的時候就使用 Dispatch Group 比較方便。
//Objective-C
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
//追加任務
dispatch_group_async(group, queue, ^{
NSLog(@"bloack0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"bloack1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"bloack2");
});
//在所有任務執行完成后,通知。。。
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");
});
//Swift
let group = DispatchGroup()
let queue = DispatchQueue.global()
queue.async(group: group) {
print("TEST0")
}
queue.async(group: group) {
print("TEST1")
}
queue.async(group: group) {
print("TEST2")
}
group.notify(queue: DispatchQueue.main) {
print("done")
}
Dispatch barrier
在一些情況下,當我們在處理一些事情的時候,要處理另外一些事情的時候,需要停止當前做的事情的時候,我們可能需要 Dispatch barrier 來幫助我們完成任務。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"read0");
});
dispatch_async(queue, ^{
NSLog(@"read1");
});
//在讀取數據的過程中,執行寫操作,就要停止讀操作,只執行寫操作,這樣不會造成數據沖突的問題。
dispatch_barrier_async(queue, ^{
NSLog(@"write");
});
dispatch_async(queue, ^{
NSLog(@"read2");
});
dispatch_async(queue, ^{
NSLog(@"read3");
});
相關資料
Swift 3必看:從使用場景了解GCD新API(卓同學)
Concurrent Programming With GCD in Swift 3
關于iOS多線程,你看我就夠了