1. GCD簡介
什么是GCD呢?我們先來看看百度百科的解釋簡單了解下概念
引自百度百科:Grand Central Dispatch(GCD) 是Apple開發的一個多核編程的較新的解決方法。它主要用于優化應用程序以支持多核處理器以及其他對稱多處理系統。它是一個在線程池模式的基礎上執行的并行任務。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。
為什么要用GCD呢?
因為GCD有很多好處啊,具體如下:
GCD可用于多核的并行運算
GCD會自動利用更多的CPU內核(比如雙核、四核)
GCD會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
程序員只需要告訴GCD想要執行什么任務,不需要編寫任何線程管理代碼
2. 任務和隊列
學習GCD之前,先來了解GCD中兩個核心概念:任務和隊列。
任務:就是執行操作的意思,換句話說就是你在線程中執行的那段代碼。在GCD中是放在block中的。執行任務有兩種方式:同步執行和異步執行。兩者的主要區別是:是否具備開啟新線程的能力。
同步執行(sync):只能在當前線程中執行任務,不具備開啟新線程的能力
異步執行(async):可以在新的線程中執行任務,具備開啟新線程的能力
隊列:這里的隊列指任務隊列,即用來存放任務的隊列。隊列是一種特殊的線性表,采用FIFO(先進先出)的原則,即新任務總是被插入到隊列的末尾,而讀取任務的時候總是從隊列的頭部開始讀取。每讀取一個任務,則從隊列中釋放一個任務。在GCD中有兩種隊列:串行隊列和并行隊列。
并行隊列(Concurrent Dispatch Queue):可以讓多個任務并行(同時)執行(自動開啟多個線程同時執行任務)
并行功能只有在異步(dispatch_async)函數下才有效
串行隊列(Serial Dispatch Queue):讓任務一個接著一個地執行(一個任務執行完畢后,再執行下一個任務)
通過上面的總結可以發現任務(同步,異步)、隊列(并發、穿行)、線程是不同的概念,千萬不要混淆;同步(或異步)執行的任務存放在并行(或串行)的隊列中在線程中執行
1. 隊列的創建方法
可以使用dispatch_queue_create來創建對象,需要傳入兩個參數,第一個參數表示隊列的唯一標識符,用于DEBUG,可為空;第二個參數用來識別是串行隊列還是并行隊列。DISPATCH_QUEUE_SERIAL表示串行隊列,DISPATCH_QUEUE_CONCURRENT表示并行隊列。
// 串行隊列的創建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行隊列的創建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
對于并行隊列,還可以使用dispatch_get_global_queue來創建全局并行隊列。GCD默認提供了全局的并行隊列,需要傳入兩個參數。第一個參數表示隊列優先級,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個參數暫時沒用,用0即可。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
});
雖然使用GCD只需兩步,但是既然我們有兩種隊列,兩種任務執行方式,那么我們就有了四種不同的組合方式。這四種不同的組合方式是
并行隊列 + 同步執行
并行隊列 + 異步執行
串行隊列 + 同步執行
串行隊列 + 異步執行
實際上,我們還有一種特殊隊列是主隊列,那樣就有六種不同的組合方式了。
主隊列 + 同步執行
主隊列 + 異步執行
上述雖然有6中組合方式,但實際上有些組合起到的實際效果是一樣的;那么這幾種不同組合方式各有什么區別呢,這里為了方便,先上結果,再來講解。為圖省事,直接查看表格結果。
下邊我們來分別講講這幾種不同的組合方式的使用方法。
4. GCD的基本使用
先來講講并行隊列的兩種使用方法。
1. 并行隊列 + 同步執行
不會開啟新線程,執行完一個任務,再執行下一個任務
- (void) syncConcurrent
{
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncConcurrent---end");
}
輸出結果:
2016-09-03 19:22:27.577 GCD[11557:1897538] syncConcurrent---begin
2016-09-03 19:22:27.578 GCD[11557:1897538] 1------{number = 1, name = main}
2016-09-03 19:22:27.578 GCD[11557:1897538] 1------{number = 1, name = main}
2016-09-03 19:22:27.578 GCD[11557:1897538] 2------{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] 2------{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] 3------{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] 3------{number = 1, name = main}
2016-09-03 19:22:27.579 GCD[11557:1897538] syncConcurrent---end
從并行隊列 + 同步執行中可以看到,所有任務都是在主線程中執行的。由于只有一個線程,所以任務只能一個一個執行。
同時我們還可以看到,所有任務都在打印的syncConcurrent---begin和syncConcurrent---end之間,這說明任務是添加到隊列中馬上執行的。
2. 并行隊列 + 異步執行
可同時開啟多線程,任務交替執行
- (void) asyncConcurrent
{
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"asyncConcurrent---end");
}
輸出結果:
2016-09-03 19:27:31.503 GCD[11595:1901548] asyncConcurrent---begin
2016-09-03 19:27:31.504 GCD[11595:1901548] asyncConcurrent---end
2016-09-03 19:27:31.504 GCD[11595:1901626] 1------{number = 2, name = (null)}
2016-09-03 19:27:31.504 GCD[11595:1901625] 2------{number = 4, name = (null)}
2016-09-03 19:27:31.504 GCD[11595:1901855] 3------{number = 3, name = (null)}
2016-09-03 19:27:31.504 GCD[11595:1901626] 1------{number = 2, name = (null)}
2016-09-03 19:27:31.504 GCD[11595:1901625] 2------{number = 4, name = (null)}
2016-09-03 19:27:31.505 GCD[11595:1901855] 3------{number = 3, name = (null)}
在并行隊列 + 異步執行中可以看出,除了主線程,又開啟了3個線程,并且任務是交替著同時執行的。
另一方面可以看出,所有任務是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執行的。說明任務不是馬上執行,而是將所有任務添加到隊列之后才開始異步執行。
接下來再來講講串行隊列的執行方法。
3. 串行隊列 + 同步執行
不會開啟新線程,在當前線程執行任務。任務是串行的,執行完一個任務,再執行下一個任務
- (void) syncSerial
{
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncSerial---end");
}
輸出結果為:
2016-09-03 19:29:00.066 GCD[11622:1903904] syncSerial---begin
2016-09-03 19:29:00.067 GCD[11622:1903904] 1------{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 1------{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 2------{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 2------{number = 1, name = main}
2016-09-03 19:29:00.067 GCD[11622:1903904] 3------{number = 1, name = main}
2016-09-03 19:29:00.068 GCD[11622:1903904] 3------{number = 1, name = main}
2016-09-03 19:29:00.068 GCD[11622:1903904] syncSerial---end
在串行隊列 + 同步執行可以看到,所有任務都是在主線程中執行的,并沒有開啟新的線程。而且由于串行隊列,所以按順序一個一個執行。
同時我們還可以看到,所有任務都在打印的syncConcurrent---begin和syncConcurrent---end之間,這說明任務是添加到隊列中馬上執行的。
4. 串行隊列 + 異步執行
會開啟新線程(1條),但是因為任務是串行的,執行完一個任務,再執行下一個任務
- (void) asyncSerial
{
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"asyncSerial---end");
}
輸出結果為:
2016-09-03 19:30:08.363 GCD[11648:1905817] asyncSerial---begin
2016-09-03 19:30:08.364 GCD[11648:1905817] asyncSerial---end
2016-09-03 19:30:08.364 GCD[11648:1905895] 1------{number = 2, name = (null)}
2016-09-03 19:30:08.364 GCD[11648:1905895] 1------{number = 2, name = (null)}
2016-09-03 19:30:08.364 GCD[11648:1905895] 2------{number = 2, name = (null)}
2016-09-03 19:30:08.364 GCD[11648:1905895] 2------{number = 2, name = (null)}
2016-09-03 19:30:08.365 GCD[11648:1905895] 3------{number = 2, name = (null)}
2016-09-03 19:30:08.365 GCD[11648:1905895] 3------{number = 2, name = (null)}
在串行隊列 + 異步執行可以看到,開啟了一條新線程,但是任務還是串行,所以任務是一個一個執行。
另一方面可以看出,所有任務是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執行的。說明任務不是馬上執行,而是將所有任務添加到隊列之后才開始同步執行。
下邊講講剛才我們提到過的特殊隊列——主隊列。
主隊列:GCD自帶的一種特殊的串行隊列
所有放在主隊列中的任務,都會放到主線程中執行
可使用dispatch_get_main_queue()獲得主隊列
我們再來看看主隊列的兩種組合方式。
5. 主隊列 + 同步執行
互等卡住不可行(在主線程中調用)
- (void)syncMain
{
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncMain---end");
}
輸出結果
2016-09-03 19:32:15.356 GCD[11670:1908306] syncMain---begin
這時候,我們驚奇的發現,在主線程中使用主隊列 + 同步執行,任務不再執行了,而且syncMain---end也沒有打印。這是為什么呢?
這是因為我們在主線程中執行這段代碼。我們把任務放到了主隊列中,也就是放到了主線程的隊列中。而同步執行有個特點,就是對于任務是立馬執行的。那么當我們把第一個任務放進主隊列中,它就會立馬執行。但是主線程現在正在處理syncMain方法,所以任務需要等syncMain執行完才能執行。而syncMain執行到第一個任務的時候,又要等第一個任務執行完才能往下執行第二個和第三個任務。
那么,現在的情況就是syncMain方法和第一個任務都在等對方執行完畢。這樣大家互相等待,所以就卡住了,所以我們的任務執行不了,而且syncMain---end也沒有打印。
要是如果不再主線程中調用,而在其他線程中調用會如何呢?
不會開啟新線程,執行完一個任務,再執行下一個任務(在其他線程中調用)
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[self syncMain];
});
輸出結果:
2016-09-03 19:32:45.496 GCD[11686:1909617] syncMain---begin
2016-09-03 19:32:45.497 GCD[11686:1909374] 1------{number = 1, name = main}
2016-09-03 19:32:45.498 GCD[11686:1909374] 1------{number = 1, name = main}
2016-09-03 19:32:45.498 GCD[11686:1909374] 2------{number = 1, name = main}
2016-09-03 19:32:45.498 GCD[11686:1909374] 2------{number = 1, name = main}
2016-09-03 19:32:45.499 GCD[11686:1909374] 3------{number = 1, name = main}
2016-09-03 19:32:45.499 GCD[11686:1909374] 3------{number = 1, name = main}
2016-09-03 19:32:45.499 GCD[11686:1909617] syncMain---end
在其他線程中使用主隊列 + 同步執行可看到:所有任務都是在主線程中執行的,并沒有開啟新的線程。而且由于主隊列是串行隊列,所以按順序一個一個執行。
同時我們還可以看到,所有任務都在打印的syncConcurrent---begin和syncConcurrent---end之間,這說明任務是添加到隊列中馬上執行的。
6. 主隊列 + 異步執行
只在主線程中執行任務,執行完一個任務,再執行下一個任務
- (void)asyncMain
{
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
????dispatch_async(queue, ^{
????????for (int i = 0; i < 2; ++i) {
????????NSLog(@"3------%@",[NSThread currentThread]);
????????}
});
????NSLog(@"asyncMain---end");
}
輸出結果:
2016-09-03 19:33:54.995 GCD[11706:1911313] asyncMain---begin
2016-09-03 19:33:54.996 GCD[11706:1911313] asyncMain---end
2016-09-03 19:33:54.996 GCD[11706:1911313] 1------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 1------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------{number = 1, name = main}
我們發現所有任務都在主線程中,雖然是異步執行,具備開啟線程的能力,但因為是主隊列,所以所有任務都在主線程中,并且一個接一個執行。
另一方面可以看出,所有任務是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執行的。說明任務不是馬上執行,而是將所有任務添加到隊列之后才開始同步執行。
自己寫了個簡單的Demo:GCD的簡單了解
弄懂了難理解、繞來繞去的隊列+任務之后,我們來學習一個簡單的東西——GCD線程之間的通訊。
6. GCD的其他方法
1. GCD的柵欄方法dispatch_barrier_async
我們有時需要異步執行兩組操作,而且第一組操作執行完之后,才能開始執行第二組操作。這樣我們就需要一個相當于柵欄一樣的一個方法將兩組異步執行的操作組給分割起來,當然這里的操作組里可以包含一個或多個任務。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄。
- (void)barrier
{
dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
}
輸出結果:
2016-09-03 19:35:51.271 GCD[11750:1914724] ----1-----{number = 2, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----2-----{number = 3, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----barrier-----{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914722] ----3-----{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914724] ----4-----{number = 2, name = (null)}
可以看出在執行完柵欄前面的操作之后,才執行柵欄操作,最后再執行柵欄后邊的操作。
2. GCD的延時執行方法dispatch_after
當我們需要延遲執行一段代碼時,就需要用到GCD的dispatch_after方法。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后異步執行這里的代碼...
NSLog(@"run-----");
});
3. GCD的一次性代碼(只執行一次)dispatch_once
我們在創建單例、或者有整個程序運行過程中只執行一次的代碼時,我們就用到了GCD的dispatch_once方法。使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行1次。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執行1次的代碼(這里面默認是線程安全的)
});
4. GCD的快速迭代方法dispatch_apply
通常我們會用for循環遍歷,但是GCD給我們提供了快速迭代的方法dispatch_apply,使我們可以同時遍歷。比如說遍歷0~5這6個數字,for循環的做法是每次取出一個元素,逐個遍歷。dispatch_apply可以同時遍歷多個數字。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd------%@",index, [NSThread currentThread]);
});
輸出結果:
2016-09-03 19:37:02.250 GCD[11764:1915764] 1------{number = 1, name = main}
2016-09-03 19:37:02.250 GCD[11764:1915885] 0------{number = 2, name = (null)}
2016-09-03 19:37:02.250 GCD[11764:1915886] 2------{number = 3, name = (null)}
2016-09-03 19:37:02.251 GCD[11764:1915764] 4------{number = 1, name = main}
2016-09-03 19:37:02.250 GCD[11764:1915884] 3------{number = 4, name = (null)}
2016-09-03 19:37:02.251 GCD[11764:1915885] 5------{number = 2, name = (null)}
從輸出結果中前邊的時間中可以看出,幾乎是同時遍歷的。
5. GCD的隊列組dispatch_group
有時候我們會有這樣的需求:分別異步執行2個耗時操作,然后當2個耗時操作都執行完畢后再回到主線程執行操作。這時候我們可以用到GCD的隊列組。
我們可以先把任務放到隊列中,然后將隊列放入隊列組中。
調用隊列組的dispatch_group_notify回到主線程執行操作。
dispatch_group_t group =? dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執行1個耗時的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執行1個耗時的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執行完畢后,回到主線程...
});dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執行1個耗時的異步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執行1個耗時的異步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執行完畢后,回到主線程...
});