概念
多線程編程 先來看一下下面的代碼糙麦。
int main() {
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
上面的代碼最終會轉(zhuǎn)化為機器能夠識別的指令(二進制代碼)。然后這些命令在 CPU 中一句一句的執(zhí)行。由于一個 CPU 一次只能執(zhí)行一個命令蕴侣,不能執(zhí)行某處分開的并列的兩個命令缠犀,因此通過 CPU 執(zhí)行的 CPU 命令就好像一條無分叉的大道数苫,其執(zhí)行不會出現(xiàn)分歧。這里所說的“一個 CPU 執(zhí)行的 CPU 命令為一條無分叉路徑”即為“線程”辨液。
在單個 CPU的情況下虐急, 使用多線程的程序可以在某個線程和其他線程之間反復(fù)的進行上下文切換,因此看上去好像一個 CPU 可以并列執(zhí)行多個線程滔迈。在具有多個 CPU 的情況下止吁,就是真的提供了多個 CPU 并行執(zhí)行多個線程的任務(wù)了。
當(dāng)然多線程編程也會帶來一定的問題:
- 多個線程更新相同的資源導(dǎo)致數(shù)據(jù)的不一致(數(shù)據(jù)競爭)
- 多個線程相互等待導(dǎo)致的死鎖問題
- 開啟多個線程會消耗大量內(nèi)存
在 iOS 程序啟動時亡鼠,最先執(zhí)行的線程赏殃,就是主線程敷待,主線程用來描繪用戶界面间涵、處理觸摸屏幕的事件等。 如果在這個線程中進行長時間的操作榜揖,就會妨礙主線程的執(zhí)行勾哩,妨礙主線程中的 RunLoop 更新,從而導(dǎo)致不能更新用戶界面举哟、應(yīng)用畫面長時間停滯等問題思劳。
GCD (Grand Central Dispatch) 通過 GCD 開發(fā)者只需要定義想要執(zhí)行的任務(wù)并追加到適當(dāng)?shù)?Dispatch Queue 中, GCD 就能生成必要的線程并計劃執(zhí)行任務(wù)妨猩。它將線程管理作為系統(tǒng)的一部分來實現(xiàn)的潜叛,因此可統(tǒng)一管理,這樣就相對來說更加有效率壶硅。
GCD 簡單使用
重要知識點
- async/sync
- sync 同步任務(wù)威兜,會堵塞當(dāng)前線程等待任務(wù)執(zhí)行完
- async 異步任務(wù),不會堵塞當(dāng)前線程庐椒,只要有任務(wù)就會繼續(xù)執(zhí)行
- Dispatch Queue(列隊)
- Serial Dispatch Queue (等待前一個任務(wù)完成后才能繼續(xù)執(zhí)行下一個任務(wù))
- Global Dispatch Queue(系統(tǒng)系統(tǒng)的全局的同步列隊)
- Concurrent Dispatch Queue(不必等待前一個任務(wù)執(zhí)行完成也可以繼續(xù)執(zhí)行下一個任務(wù))
- Main Dispatch Queue(系統(tǒng)主線程椒舵,主要繪制用戶界面、處理屏幕事件)
- Serial Dispatch Queue (等待前一個任務(wù)完成后才能繼續(xù)執(zhí)行下一個任務(wù))
使用方法: 將指定的任務(wù)追加到適當(dāng)?shù)?Dispatch Queue 中
//Swift
DispatchQueue.global().async {
/**
*想要執(zhí)行的任務(wù)
*/
}
//Objective-C
dispatch_async(queue, ^{
/*
*想要執(zhí)行的任務(wù)
*/
})
創(chuàng)建列隊
- 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 一旦生成并且追加處理约谈,系統(tǒng)對于一個 Serial Dispatch Queue 就只生成并使用一個線程笔宿。如果過多使用多線程,會消耗大量內(nèi)存棱诱,引起大量的上下文切換泼橘,大幅度江都一同的響應(yīng)性能。
- 為了避免多線程更新相同資源導(dǎo)致數(shù)據(jù)競爭問題迈勋,使用 Serial Dispatch Queue炬灭。
- Dispatch Queue 的名稱推薦使用應(yīng)用程序ID的逆序全程域名,這么做的好處是在Xcode 和 Instruments 調(diào)試的時候很方便觀察粪躬。另外應(yīng)用奔潰時產(chǎn)生的 CrashLog 中很容易找到對應(yīng)線程担败。
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 是主線程中執(zhí)行的 Dispatch Queue 昔穴,是一個 Serial Dispatch Queue。追加到 Main Dispatch Queue 的處理在主線程的 RunLoop 中執(zhí)行提前。主線程主要處理用戶界面的渲染吗货、觸摸事件等, 所以需要長時間的操作狈网,最好在別的線程中執(zhí)行宙搬,執(zhí)行完后再在主線程中刷新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中優(yōu)先級是一個 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 }
}
- 我們可以直接使用系統(tǒng)提供的這兩個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(), ^{
//只能在主線程中執(zhí)行的操作
});
});
//Swift
//Global Dispatch Queue
DispatchQueue.global().async {
//處理長時間的操作
.....
//使用Main Dispatch Queue 在主線程中操作
DispatchQueue.main.async {
//只能在主線程中執(zhí)行的操作
}
}
- Dispatch After
當(dāng)需要延遲執(zhí)行的情況就要用到 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(), ^{
..........
});
-
第一個參數(shù)是指定需要延遲的時間士鸥,是dispatch_time_t 類型的值闲孤。
dispatch_time 方法的第一個參數(shù)是 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 方法的第二個參數(shù)有以下幾種類型:
#define NSEC_PER_SEC 1000000000ull
#define NSEC_PER_MSEC 1000000ull
#define USEC_PER_SEC 1000000ull
#define NSEC_PER_USEC 1000ull
ull
表示 unsigned long long
。
第二個參數(shù)是需要追加處理的 Dispatch Queue烤礁。
第三個參數(shù)是需要追加的 Block讼积。
//Swift
let delay = DispatchTime.now() + DispatchTimeInterval.seconds(60)
//let delay = DispatchTime.now() + 3.0
DispatchQueue.main.asyncAfter(deadline: delay) {
//需要延遲執(zhí)行的任務(wù)
}
- 在定義延遲時間的時候可以直接在 DispatchTime.now() 方法后面加上需要的延遲的時間,是因為蘋果定義了
+
方法脚仔,使得它可以直接操作勤众。
//Swift
public func +(time: DispatchTime, seconds: Double) -> DispatchTime
當(dāng)然還定義了一些其他的方法:
//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
如果有這樣的需求,需要等待所有的線程中的任務(wù)都執(zhí)行完成后鲤脏,做統(tǒng)一的處理们颜。通常為多個網(wǎng)絡(luò)請求操作,需要等待所有的網(wǎng)絡(luò)請求完成后猎醇,解析數(shù)據(jù)窥突,統(tǒng)一刷新UI。這時候我們就可以通過 Dispatch queue 來完成我們的操作姑食。當(dāng)然在使用一個 Serial Dispatch Queue 的時候只要將所有的任務(wù)追加到其中并且在最后處理結(jié)果即可實現(xiàn)波岛。但是在使用 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();
//追加任務(wù)
dispatch_group_async(group, queue, ^{
NSLog(@"bloack0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"bloack1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"bloack2");
});
//在所有任務(wù)執(zhí)行完成后音半,通知则拷。。曹鸠。
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
在一些情況下煌茬,當(dāng)我們在處理一些事情的時候,要處理另外一些事情的時候彻桃,需要停止當(dāng)前做的事情的時候坛善,我們可能需要 Dispatch barrier 來幫助我們完成任務(wù)。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"read0");
});
dispatch_async(queue, ^{
NSLog(@"read1");
});
//在讀取數(shù)據(jù)的過程中,執(zhí)行寫操作眠屎,就要停止讀操作剔交,只執(zhí)行寫操作,這樣不會造成數(shù)據(jù)沖突的問題改衩。
dispatch_barrier_async(queue, ^{
NSLog(@"write");
});
dispatch_async(queue, ^{
NSLog(@"read2");
});
dispatch_async(queue, ^{
NSLog(@"read3");
});
相關(guān)資料
Swift 3必看:從使用場景了解GCD新API(卓同學(xué))
Concurrent Programming With GCD in Swift 3
關(guān)于iOS多線程岖常,你看我就夠了