GCD 原理詳解
GCD 簡介
GCD(Grand Central Dispatch)是 Apple 開發的一個多核程式設計的解決方法。它主要用於優化應用程式以支援多核處理器以及其他對稱多處理系統。
GCD 基本概念
GCD 主要包含兩個核心概念:任務 和 佇列。
任務
任務:即要線上程中執行的那段程式碼。GCD 將任務定義在 block 中。
任務的執行主要有兩種方式:同步執行(sync) 和 非同步執行(async)。兩者的主要區別是:是否等待佇列中的任務執行結束,是否具備開啟新執行緒的能力。因此,根據任務的執行方式可以將任務分成兩種型別:
同步任務(sync)
- 同步新增任務到指定的佇列中,在新增的任務執行結束之前,會一直等待,直到佇列裡面的任務完成之後再繼續執行。
- 只能在當前執行緒中執行任務,不具備開啟新執行緒的能力。
非同步任務(async)
- 非同步新增任務到指定的佇列中,它不會做任何等待,可以繼續執行任務。
- 可以在新的執行緒中執行任務,具備開啟新執行緒的能力。
注意: 非同步任務(async) 雖然具有開啟新執行緒的能力,但是並不一定開啟新執行緒。這跟任務所指定的佇列型別有關(下面會講)。
佇列
佇列(Dispatch Queue):即用來存放任務的佇列。佇列是一種特殊的線性表,採用 FIFO(先進先出)的原則,即新任務總是被插入到佇列的末尾,而讀取任務的時候總是從佇列的頭部開始讀取。每讀取一個任務,則從佇列中釋放一個任務。佇列的結構如下圖所示:
在 GCD 中有兩種佇列:序列佇列 和 併發佇列。兩者的主要區別是:執行順序不同,開啟執行緒數不同。
序列佇列
-
每次只有一個任務被執行。(只開啟一個執行緒,一個任務執行完畢後,在執行下一個任務)
併發佇列
-
允許多個任務(同時)執行。(可以開啟多個執行緒,並同時執行任務)
注意:併發佇列 的併發功能只有在非同步(dispatch_async)函式下才有效。
GCD 使用方法
GCD 的使用主要包含兩個步驟:
- 建立一個佇列(序列佇列或併發佇列)
- 將任務追加到任務的等待佇列中,然後系統會根據任務型別執行任務(同步執行或非同步執行)
佇列的建立/獲取
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
dispatch_queue_attr_t _Nullable attr)
引數說明:
-
label
:表示佇列的唯一識別符號,用於 DEBUG,可為空。 -
attr
:表示佇列的型別。DISPATCH_QUEUE_SERIAL
表示序列佇列;DISPATCH_QUEUE_CONCURRENT
表示併發佇列。
// 序列佇列的建立方法
dispatch_queue_t queue = dispatch_queue_create("me.chuquan.testQueue", DISPATCH_QUEUE_SERIAL);
// 併發佇列的建立方法
dispatch_queue_t queue = dispatch_queue_create("me.chuquan.testQueue", DISPATCH_QUEUE_CONCURRENT);
對於序列佇列,GCD 提供了一種特殊的序列佇列:主佇列(Main Dispatch Queue)。
- 所有放在主佇列的任務,都會在主執行緒執行。
- 可使用
dispatch_get_main_queue()
獲取主佇列。
// 主佇列的獲取方法
dispatch_queue_t queue = dispatch_get_main_queue();
對於併發佇列,GCD 預設提供了 全域性併發佇列(Global Dispatch Queue)。
- 可使用
dispatch_get_global_queue
獲取全域性併發佇列。
// 全域性併發佇列的獲取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
GCD 提供了 4 個 全域性併發佇列,分別對應不同的優先順序。
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
-
DISPATCH_QUEUE_PRIORITY_BACKGROUND
:後臺佇列
任務的建立
GCD 提供了同步執行任務的建立方法 dispatch_sync
和非同步執行任務建立方法 dispatch_async
。
// 同步任務建立方法
dispatch_sync(queue, ^{
// 這裡放同步執行任務程式碼
});
// 非同步任務建立方法
dispatch_async(queue, ^{
// 這裡放非同步執行任務程式碼
});
GCD 使用組合
GCD 有兩種佇列(序列佇列/併發佇列),兩種任務(同步任務/非同步任務),可以得到 4 種不同的使用組合。
- 同步任務 + 併發佇列
- 非同步任務 + 併發佇列
- 同步任務 + 序列佇列
- 非同步任務 + 序列佇列
實際上,前文還提到兩種特殊的佇列:全域性併發佇列、主佇列。全域性併發佇列可作為普通併發佇列使用。但是主佇列比較特殊,因此又得到 2 種組合:
- 同步任務 + 主佇列
- 非同步任務 + 主佇列
同步執行 + 併發佇列
/**
* 同步任務 + 併發佇列
* 特點:在當前執行緒中執行任務,不會開啟新執行緒,執行完一個任務,再執行下一個任務。
*/
- (void)syncConcurrent {
NSLog(@"currentThread---%@", [NSThread currentThread]); // 列印當前執行緒
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("me.chuquan.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_sync(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_sync(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 列印當前執行緒
}
});
NSLog(@"syncConcurrent---end");
}
上圖所示為 同步任務 + 併發佇列
的工作原理。
-
syncConcurrent
被新增至主佇列中,在主執行緒執行。 - 同步任務被新增至並行佇列,並行佇列允許多工同時執行,但由於加入的佇列是同步任務,不會開啟新執行緒,在主執行緒執行。
-
syncConcurrent
會被同步任務阻塞。
執行結果:
currentThread---<NSThread: 0x60000068ee80>{number = 1, name = main}
syncConcurrent---begin
1---<NSThread: 0x60000068ee80>{number = 1, name = main}
1---<NSThread: 0x60000068ee80>{number = 1, name = main}
2---<NSThread: 0x60000068ee80>{number = 1, name = main}
2---<NSThread: 0x60000068ee80>{number = 1, name = main}
3---<NSThread: 0x60000068ee80>{number = 1, name = main}
3---<NSThread: 0x60000068ee80>{number = 1, name = main}
syncConcurrent---end
非同步任務 + 併發佇列
/**
* 非同步任務 + 併發佇列
* 特點:可以開啟多個執行緒,任務交替(同時)執行。
*/
- (void)asyncConcurrent {
NSLog(@"currentThread---%@", [NSThread currentThread]); // 列印當前執行緒
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("me.chuquan.testQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
NSLog(@"asyncConcurrent---end");
}
上圖所示為 非同步任務 + 並行佇列
的工作原理。
-
asyncConcurrent
被新增至主佇列中,在主執行緒執行。 - 非同步任務被新增至並行佇列,並行佇列允許多工同時執行,且非同步任務可以開啟新執行緒,因此每個非同步任務都能啟動一個獨立的執行緒執行。
-
asyncConcurrent
不會被非同步任務阻塞。
執行結果
currentThread---<NSThread: 0x600003e6d580>{number = 1, name = main}
asyncConcurrent---begin
asyncConcurrent---end
1---<NSThread: 0x600003ec2a80>{number = 5, name = (null)}
2---<NSThread: 0x600003ecce40>{number = 3, name = (null)}
3---<NSThread: 0x600003eccec0>{number = 4, name = (null)}
2---<NSThread: 0x600003ecce40>{number = 3, name = (null)}
3---<NSThread: 0x600003eccec0>{number = 4, name = (null)}
1---<NSThread: 0x600003ec2a80>{number = 5, name = (null)}
同步任務 + 序列佇列
/**
* 同步任務 + 序列佇列
* 特點:不會開啟新執行緒,在當前執行緒執行任務。任務是序列的,執行完一個任務,再執行下一個任務。
*/
- (void)syncSerial {
NSLog(@"currentThread---%@", [NSThread currentThread]); // 列印當前執行緒
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("me.chuquan.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_sync(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_sync(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
NSLog(@"syncSerial---end");
}
上圖所示為 同步任務 + 序列佇列
的工作原理。
-
syncSerial
被新增至主佇列中,在主執行緒執行。 - 同步任務被新增至序列佇列,序列佇列不允許多工同時執行,因此同步任務在當前執行緒執行(主執行緒)。
-
syncSerial
會被同步任務阻塞。
非同步任務 + 序列佇列
/**
* 非同步任務 + 序列佇列
* 特點:會開啟新執行緒,但是因為任務是序列的,執行完一個任務,再執行下一個任務。
*/
- (void)asyncSerial {
NSLog(@"currentThread---%@", [NSThread currentThread]); // 列印當前執行緒
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("me.chuquan.testQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
NSLog(@"asyncSerial---end");
}
上圖所示為 非同步任務 + 序列佇列
的工作原理。
-
asyncSerial
被新增至主佇列中,在主執行緒執行。 - 非同步任務被新增至序列佇列,非同步任務能開啟新執行緒,但是序列佇列不允許多工,所以只能開啟一條新執行緒。
-
asyncSerial
不會被非同步任務阻塞。
執行結果
currentThread---<NSThread: 0x600001ef5d00>{number = 1, name = main}
asyncSerial---begin
asyncSerial---end
1---<NSThread: 0x600001e5a740>{number = 3, name = (null)}
1---<NSThread: 0x600001e5a740>{number = 3, name = (null)}
2---<NSThread: 0x600001e5a740>{number = 3, name = (null)}
2---<NSThread: 0x600001e5a740>{number = 3, name = (null)}
3---<NSThread: 0x600001e5a740>{number = 3, name = (null)}
3---<NSThread: 0x600001e5a740>{number = 3, name = (null)}
同步任務 + 主佇列
/**
* 同步任務 + 主佇列
* 特點(主執行緒呼叫):互等卡主不執行。
* 特點(其他執行緒呼叫):不會開啟新執行緒,執行完一個任務,再執行下一個任務。
*/
- (void)syncMain {
NSLog(@"currentThread---%@", [NSThread currentThread]); // 列印當前執行緒
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_sync(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_sync(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
NSLog(@"syncMain---end");
}
上圖所示為 同步任務 + 主佇列
的工作原理。
-
syncMain
被新增至主佇列中,在主執行緒執行。 - 同步任務被新增至主佇列,同步任務不會開啟新執行緒,且主佇列(屬於序列佇列)中的任務只能在主執行緒執行。
-
syncMain
會被同步任務阻塞。但是需要注意的是syncMain
和同步任務均在主佇列中,同步任務需要等待syncMain
執行完畢,因此產生死鎖。
執行結果
崩潰
對於這種情況,可以將 syncMain
放置新執行緒執行以避免產生死鎖:
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
非同步任務 + 主佇列
/**
* 非同步任務 + 主佇列
* 特點:只在主執行緒中執行任務,執行完一個任務,再執行下一個任務
*/
- (void)asyncMain {
NSLog(@"currentThread---%@", [NSThread currentThread]); // 列印當前執行緒
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
dispatch_async(queue, ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@", [NSThread currentThread]); // 列印當前執行緒
}
});
NSLog(@"asyncMain---end");
}
上圖所示為 非同步任務 + 主佇列
的工作原理。
-
asyncMain
被新增至主佇列中,在主執行緒執行。 - 非同步任務被新增至主佇列,非同步任務能開啟新執行緒,但是主佇列(屬於序列佇列)中的任務只能在主執行緒執行。
-
asyncMain
不會被非同步任務阻塞。
執行結果
currentThread---<NSThread: 0x6000014d3700>{number = 1, name = main}
asyncMain---begin
asyncMain---end
1---<NSThread: 0x6000014d3700>{number = 1, name = main}
1---<NSThread: 0x6000014d3700>{number = 1, name = main}
2---<NSThread: 0x6000014d3700>{number = 1, name = main}
2---<NSThread: 0x6000014d3700>{number = 1, name = main}
3---<NSThread: 0x6000014d3700>{number = 1, name = main}
3---<NSThread: 0x6000014d3700>{number = 1, name = main}
GCD 應用
執行緒間通訊
/**
* 執行緒間通訊
*/
- (void)communication {
// 獲取全域性併發佇列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 獲取主佇列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(queue, ^{
// 非同步追加任務
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@", [NSThread currentThread]); // 列印當前執行緒
}
// 回到主執行緒
dispatch_async(mainQueue, ^{
// 追加在主執行緒中執行的任務
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@", [NSThread currentThread]); // 列印當前執行緒
});
});
}
上圖所示為執行緒間通訊的工作原理。
-
communication
被新增至主佇列中,在主執行緒執行。 - 非同步任務被新增至全域性佇列(並行佇列),非同步任務能開啟新執行緒,因此非同步任務在新執行緒執行。
-
communication
不會被非同步任務阻塞。 - 當非同步任務執行完成後,再將一個非同步任務新增至主佇列,主佇列中的任務都在主執行緒執行,所以該非同步任務在主執行緒執行。從而達到執行緒間通訊的目的。
執行結果
1---<NSThread: 0x60000227ec80>{number = 3, name = (null)}
1---<NSThread: 0x60000227ec80>{number = 3, name = (null)}
2---<NSThread: 0x6000022ddd00>{number = 1, name = main}
參考
相關文章
- iOS GCD詳解iOSGC
- iOS多執行緒:GCD詳解iOS執行緒GC
- iOS 中的 GCD 實現詳解iOSGC
- GCD原始碼原理分析GC原始碼
- GoPlay 原理詳解Go
- Go Context 原理詳解GoContext
- 比特幣原理詳解比特幣
- Java CAS 原理詳解Java
- Webpack Tapable原理詳解Web
- SpringMVC工作原理詳解SpringMVC
- CTMediator 原理詳解(一)
- CTMediator 原理詳解(二)
- HashMap原理詳解,包括底層原理HashMap
- GCD Inside: GCD 宏GCIDE
- JUC---ThreadLocal原理詳解thread
- Apollo功能及原理詳解
- DNS 查詢原理詳解DNS
- 快速生成樹原理詳解
- Kerberos認證原理詳解ROS
- volatile底層原理詳解
- 詳解MySQL事務原理MySql
- socket.io 原理詳解
- engine.io 原理詳解
- Flashback Data Archive原理詳解Hive
- Tomcat結構原理詳解Tomcat
- Swift GCD 瞭解一下SwiftGC
- iOS 多執行緒:『GCD』詳盡總結iOS執行緒GC
- 詳解 php 反射機制原理PHP反射
- Seq2Seq原理詳解
- Express中介軟體原理詳解Express
- JavaScript物件導向詳解(原理)JavaScript物件
- 詳解Spring Retry實現原理Spring
- GCD Inside: GCD 資料結構GCIDE資料結構
- POE 供電裝置原理詳解
- 從原理到應用,Elasticsearch詳解Elasticsearch
- MySQL中count(*)函式原理詳解MySql函式
- 詳細講解函式呼叫原理函式
- 主成分分析(PCA)原理詳解PCA