一、簡介
是基於C語言開發的一套多線程開發機制,也是目前蘋果官方推薦的多線程開發方法,用起來也最簡單,只是它基於C語言開發,並不像NSOperation是面向對象的開發,而是完全面向過程的。如果使用GCD,完全由系統管理線程,我們不需要編寫線程代碼。只需定義想要執行的任務,然後添加到適當的調度隊列(dispatch_queue).GCD會負責創建線程和調度你的任務,系統會直接提供線程管理。
二、任務和隊列
GCD中有兩個核心概念
(1)任務:執行什麼操作
(2)隊列:用來存放任務
GCD的使用就兩個步驟
(1)定制任務
(2)確定想做的事情
將任務添加到隊列中,GCD會自動將隊列中的任務取出,放到對應的線程中執行
提示:任務的取出遵循隊列的FIFO原則:先進先出,後進後出
三、執行任務
1、GCD中有2個用來執行任務的函數
說明:把右邊的參數(任務)提交給左邊的參數(隊列)進行執行
(1)用同步的方式執行任務 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
參數說明:queue:隊列; block:任務
(2)用異步的方式執行任務 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
2、同步和異步的區別
同步:在當前線程中執行
異步:在另一條線程中執行
四、隊列
1、GCD的隊列可以分為2大類型
(1)並發隊列(Concurrent Dispatch Queue):可以讓多個任務並發(同時)執行(自動開啟多個線程同時執行任務)並發功能只有在異步(dispatch_async)函數才有效
(2)串行隊列(Serial Dispatch Queue):讓任務一個接著一個地執行(一個任務執行完畢後,再執行下一個任務)
2、補充說明
有4個術語比較容易混淆:同步、異步、並發、串行(在博客 多線程編程(-)—-概念 也提到了)
同步和異步決定了要不要開啟新的線程
同步:在當前線程中執行任務,不具備開啟新線程的能力
異步:在新的線程中執行任務,具備開啟新線程的能力
並發和串行決定了任務的執行方式
並發:多個任務並發執行
串行:一個任務執行完畢後,再執行下一個任務
五、(同步/異步)串行隊列和(同步/異步)並發隊列開啟線程的總結 (代碼示例)
說明:
(1)同步函數不具備開啟線程的能力,無論是什麼隊列都不會開啟線程;異步函數具備開啟線程的能力,開啟幾條線程有隊列決定(串行隊列只會開啟一條新的線程,並發隊列會開啟多條線程)
(2) MRC下凡是函數中,各種函數名中帶有create\copy\new\retain等字眼,都需要在不需要使用這個數據的時候進行release
ARC下GCD的數據類型不需要再作release
CF(core Foundation)的數據在ARC環境下還是需要release
(3) 異步函數具備開線程的能力,但不一定會開線程
1、異步並發隊列(同時開啟N個線程)

/**
* 異步並發隊列
*/
- (void)asynchronousConcurrent{
NSLog(@"異步函數往並發隊列中添加任務");
NSLog(@"主線程1111 ---- %@", [NSThread currentThread]);
// 1、創建並發隊列
// 方法一 和創建串行隊列一樣
// dispatch_queue_t queue = dispatch_queue_create("asynConcurrent", DISPATCH_QUEUE_CONCURRENT);
// 方法二 獲取全局並發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2、添加任務到隊列
dispatch_async(queue, ^{
NSLog(@"下載圖片1 ------ %@", [NSThread currentThread]);
[self loadImage:1];
});
dispatch_async(queue, ^{
NSLog(@"下載圖片2------ %@", [NSThread currentThread]);
[self loadImage:2];
});
dispatch_async(queue, ^{
NSLog(@"下載圖片3 ------ %@", [NSThread currentThread]);
[self loadImage:3];
});
NSLog(@"主線程2222 ---- %@", [NSThread currentThread]);
// 打印結果 開啟多個線程,並發執行。沒有先後順序(有的話也是剛好巧合而已) 可以看number 可以看做線程值
/**
第一次執行
2016-08-24 12:53:24.026 YJGCDDemo[1220:24092] 異步函數往並發隊列中添加任務
2016-08-24 12:53:24.027 YJGCDDemo[1220:24092] 主線程1111 ---- {number = 1, name = main}
2016-08-24 12:53:24.027 YJGCDDemo[1220:24092] 主線程2222 ---- {number = 1, name = main}
2016-08-24 12:53:24.027 YJGCDDemo[1220:24126] 下載圖片1 ------ {number = 2, name = (null)}
2016-08-24 12:53:24.027 YJGCDDemo[1220:24128] 下載圖片2------ {number = 3, name = (null)}
2016-08-24 12:53:24.027 YJGCDDemo[1220:24127] 下載圖片3 ------ {number = 4, name = (null)}
第二次執行
2016-08-24 12:53:32.427 YJGCDDemo[1220:24092] 異步函數往並發隊列中添加任務
2016-08-24 12:53:32.427 YJGCDDemo[1220:24092] 主線程1111 ---- {number = 1, name = main}
2016-08-24 12:53:32.427 YJGCDDemo[1220:24092] 主線程2222 ---- {number = 1, name = main}
2016-08-24 12:53:32.427 YJGCDDemo[1220:24126] 下載圖片2------ {number = 2, name = (null)}
2016-08-24 12:53:32.427 YJGCDDemo[1220:24260] 下載圖片3 ------ {number = 7, name = (null)}
2016-08-24 12:53:32.427 YJGCDDemo[1220:24153] 下載圖片1 ------ {number = 6, name = (null)}
*/
}
2、異步串行隊列(會開啟線程,但是只開啟一個線程)

/**
* 異步串行隊列
*/
- (void)asynchronousSerial{
NSLog(@"用異步函數往串行隊列中添加任務");
NSLog(@"主線程1111 ---- %@", [NSThread currentThread]);
//1. 創建串行隊列
dispatch_queue_t queue = dispatch_queue_create("asynSerial", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"下載圖片1 --- %@", [NSThread currentThread]);
[self loadImage:1];
});
dispatch_async(queue, ^{
NSLog(@"下載圖片2 --- %@", [NSThread currentThread]);
[self loadImage:2];
});
dispatch_async(queue, ^{
NSLog(@"下載圖片3 --- %@", [NSThread currentThread]);
[self loadImage:3];
});
NSLog(@"主線程2222 ---- %@", [NSThread currentThread]);
// 打印結果:異步串行隊列,會開啟一個線程,順序執行。 看運行結果也可以看出,圖片是一張下載完在下載下一張的。
/**
2016-08-24 12:39:14.195 YJGCDDemo[942:16507] 用異步函數往串行隊列中添加任務
2016-08-24 12:39:14.196 YJGCDDemo[942:16507] 主線程1111 ---- {number = 1, name = main}
2016-08-24 12:39:14.196 YJGCDDemo[942:16507] 主線程2222 ---- {number = 1, name = main}
2016-08-24 12:39:14.196 YJGCDDemo[942:16622] 下載圖片1 --- {number = 2, name = (null)}
2016-08-24 12:39:14.261 YJGCDDemo[942:16622] 下載圖片2 --- {number = 2, name = (null)}
2016-08-24 12:39:14.303 YJGCDDemo[942:16622] 下載圖片3 --- {number = 2, name = (null)}
*/
}
3、同步並發隊列(不會開啟新的線程,並發隊列失去並發的功能)

/**
* 同步並發隊列
*/
- (void)synchronousConcurrent{
NSLog(@"用同步函數往並發隊列中添加任務");
NSLog(@"主線程1111 ---- %@", [NSThread currentThread]);
// 1.創建並發隊列
// 方式一 一般都使用這種方式獲取
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 方式二 和創建串行隊列一樣
dispatch_queue_t queue = dispatch_queue_create("syncConcurrentncy", DISPATCH_QUEUE_CONCURRENT);
// 2.加添任務到隊列
dispatch_sync(queue, ^{
NSLog(@"下載圖片1 ---- %@", [NSThread currentThread]);
[self loadImage:1];
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片2 ---- %@", [NSThread currentThread]);
[self loadImage:2];
});
dispatch_sync(queue, ^{
NSLog(@"下載圖片3 ---- %@", [NSThread currentThread]);
[self loadImage:3];
});
NSLog(@"主線程2222 ---- %@", [NSThread currentThread]);
// 打印結果 和同步串行隊列一樣 這邊並發隊列效果失效,不會開啟線程。
/**
2016-08-24 11:20:44.153 YJGCDDemo[43913:3002870] 用同步函數往並發隊列中添加任務
2016-08-24 11:20:44.154 YJGCDDemo[43913:3002870] 主線程1111 ---- {number = 1, name = main}
2016-08-24 11:20:44.154 YJGCDDemo[43913:3002870] 下載圖片1 ---- {number = 1, name = main}
2016-08-24 11:20:44.433 YJGCDDemo[43913:3002870] 下載圖片2 ---- {number = 1, name = main}
2016-08-24 11:20:44.475 YJGCDDemo[43913:3002870] 下載圖片3 ---- {number = 1, name = main}
2016-08-24 11:20:44.508 YJGCDDemo[43913:3002870] 主線程2222 ---- {number = 1, name = main}
*/
}
4、同步串行隊列(不會開啟新的線程)

/**
* 同步串行隊列
*/
- (void)synchronousSerial{
NSLog(@"用同步函數往串行隊列中添加任務");
NSLog(@"主線程111----- %@", [NSThread currentThread]);
// 1、創建串行隊列 // DISPATCH_QUEUE_SERIAL 串行隊列 也可以為NULL NULL時 默認是 串行隊列; DISPATCH_QUEUE_CONCURRENT 並發隊列
dispatch_queue_t queue = dispatch_queue_create("syncSerial", DISPATCH_QUEUE_SERIAL);
// 2、添加任務到隊列中執行
dispatch_sync(queue, ^{
// 此塊代碼沒有任何意義,只是為了體現同步串行隊列的效果, 只能執行完了 才能執行面下的,
for (int i = 0; i < 30000; i++) {
//
NSLog(@"%i", i);
}
NSLog(@"下載圖片1 ---- %@", [NSThread currentThread]);
[self loadImage:1];
});
dispatch_sync(queue, ^{
for (int i = 0; i < 30000; i++) {
//
NSLog(@"%i", i);
}
NSLog(@"下載圖片2 -- %@", [NSThread currentThread]);
[self loadImage:2];
});
dispatch_sync(queue, ^{
for (int i = 0; i < 30000; i++) {
//
NSLog(@"%i", i);
}
NSLog(@"下載圖片3 -- %@", [NSThread currentThread]);
[self loadImage:3];
});
NSLog(@"主線程222----- %@", [NSThread currentThread]);
// 打印結果就是同步按順序執行。 每個任務按順序執行,不開啟線程
/**
2016-08-24 10:52:55.049 YJGCDDemo[43413:2986818] 用同步函數往串行隊列中添加任務
2016-08-24 10:52:55.049 YJGCDDemo[43413:2986818] 主線程111----- {number = 1, name = main}
2016-08-24 10:52:55.050 YJGCDDemo[43413:2986818] 下載圖片1 ---- {number = 1, name = main}
2016-08-24 10:52:55.580 YJGCDDemo[43413:2986818] 下載圖片2 -- {number = 1, name = main}
2016-08-24 10:52:55.616 YJGCDDemo[43413:2986818] 下載圖片3 -- {number = 1, name = main}
2016-08-24 10:52:55.644 YJGCDDemo[43413:2986818] 主線程222----- {number = 1, name = main}
*/
}
六、常用方法 在Demo中的CommonMethodsViewCotroller類
1、後台運行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// something
});
2、主線程執行
dispatch_async(dispatch_get_main_queue(), ^{
// something
});
3、一次性執行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be execution once
NSLog(@"改行代碼只執行一次");
});
4、延遲N秒執行 (這邊列舉了四種方式)

NSLog(@"打印線程----- %@", [NSThread currentThread]);
// 延時執行方式一 使用NSObject的方法
// 2秒後再調用self的run方法
// [self performSelector:@selector(loadImage) withObject:nil afterDelay:2.0];
// 延遲執行方式二 使用GCD函數
// 在同步函數中執行
// 注意 如果使用異步函數 dispatch_async 那麼[self performSelector:@selector(loadImage) withObject:nil afterDelay:5.0]; 不會被執行
// dispatch_queue_t queue = dispatch_queue_create("yangjian.net.cn", 0);
// dispatch_sync(queue, ^{
// [self performSelector:@selector(loadImage) withObject:nil afterDelay:2.0];
// });
// 延遲執行方式三 可以安排其線程---> 主隊列
// dispatch_queue_t queue = dispatch_get_main_queue();
// dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
// NSLog(@"主隊列--延遲執行------%@",[NSThread currentThread]);
// [self gcdLoadImage];
// });
// 延遲執行方式四 可以安排其線程---> 並發隊列
//1、獲取全局並發隊列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//2、計算任務執行的時間
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));
//3、會在when這個時間點,執行queue中的這個任務
dispatch_after(when, queue, ^{
NSLog(@"並發隊列--延遲執行 ---- %@", [NSThread currentThread]);
[self gcdLoadImage];
});
5、執行某個代碼片段N次
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(4, globalQueue, ^(size_t index) {
// 執行4次
});
6、隊列組的使用
// 需求
//1 分別異步執行2個耗時的操作
//2 等兩個異步操作都執行完畢後,再回到主線程執行操作
// 如果想要快速高效地實現上述需求,可以考慮用隊列組
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 並發執行的線程一
});
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
// 並發執行的線程二
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
// 等前面的異步操作都執行完畢後,回到主線程
});
七、代碼示例 在Demo中的CommonMethodsViewCotroller類
下載兩張照片完後,合並照片。(兩種方式)

/**
* 合並圖片(方式一)
*/
- (void)mergeImage{
// // 獲取全局並發隊列
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//
// // 獲取主隊列
// dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(global_queue, ^{
// 下載圖片1
UIImage *image1 = [self requestImageData:@"http://h.hiphotos.baidu.com/image/pic/item/dc54564e9258d109a4d1165ad558ccbf6c814d23.jpg"];
NSLog(@"圖片1下載完成----%@", [NSThread currentThread]);
UIImage *image2 = [self requestImageData:@"http://5.26923.com/download/pic/000/335/06efd7b7d40328f1470d4fd99a214243.jpg"];
NSLog(@"圖片2下載完成----%@", [NSThread currentThread]);
// 回到主線程顯示圖片
dispatch_async(main_queue, ^{
NSLog(@"顯示圖片---%@", [NSThread currentThread]);
self.imageViewOne.image = image1;
self.imageViewTwo.image = image2;
// 合並兩張圖片
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0.0);
[image1 drawAsPatternInRect:CGRectMake(0, 0, 100, 200)];
[image2 drawAsPatternInRect:CGRectMake(100, 0, 100, 200)];
self.imageViewThree.image = UIGraphicsGetImageFromCurrentImageContext();
// 關閉上下文
UIGraphicsEndImageContext();
NSLog(@"圖片合並完成----%@", [NSThread currentThread]);
});
});
// 打印結果 需要等圖片1下載完 在下載圖片2 再回到主線程
/**
2016-08-24 16:58:40.123 YJGCDDemo[3480:125975] 圖片1下載完成----{number = 3, name = (null)}
2016-08-24 16:58:40.319 YJGCDDemo[3480:125975] 圖片2下載完成----{number = 3, name = (null)}
2016-08-24 16:58:40.319 YJGCDDemo[3480:125910] 顯示圖片---{number = 1, name = main}
2016-08-24 16:58:40.335 YJGCDDemo[3480:125910] 圖片合並完成----{number = 1, name = main}
*/
// 效率不高 需要等圖片1,圖片2都下載完了後才合並
// 優化 使用隊列組可以讓圖片1 圖片2的下載任務同事進行,且當兩個下載任務都完成的時候回到主線程進行顯示。
}
/**
* 使用隊列組解決(方式二)
*/
- (void)groupMergeImage{
//步驟
// 1、創建一個隊列組
// 2、開啟一個任務下載圖片1
// 3、開啟一個任務下載圖片2
// 同時執行下載圖片1 和 下載圖片2操作
// 4、等group中的所有任務都執行完畢,再回到主線程執行其他操作
// 1、創建一個隊列租
dispatch_group_t group = dispatch_group_create();
// 2、開啟一個任務下載圖片1
__block UIImage *image1 = nil;
dispatch_group_async(group, global_queue, ^{
image1 = [self requestImageData:@"http://h.hiphotos.baidu.com/image/pic/item/dc54564e9258d109a4d1165ad558ccbf6c814d23.jpg"];
NSLog(@"圖片1下載完成--- %@", [NSThread currentThread]);
});
// 3、開啟一個任務下載圖片2
__block UIImage *image2 = nil;
dispatch_group_async(group, global_queue, ^{
image2 = [self requestImageData:@"http://5.26923.com/download/pic/000/335/06efd7b7d40328f1470d4fd99a214243.jpg"];
NSLog(@"圖片2下載完成--- %@", [NSThread currentThread]);
});
// 同時執行下載圖片1\下載圖片2操作
// 4、等group重的所有任務都執行完畢,再回到主線程執行其他操作
dispatch_group_notify(group, main_queue, ^{
NSLog(@"顯示圖 --- %@", [NSThread currentThread]);
self.imageViewOne.image = image1;
self.imageViewTwo.image = image2;
// 合並兩張圖片
UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 200), NO, 0.0);
[image1 drawAsPatternInRect:CGRectMake(0, 0, 100, 200)];
[image2 drawAsPatternInRect:CGRectMake(100, 0, 100, 200)];
self.imageViewThree.image = UIGraphicsGetImageFromCurrentImageContext();
// 關閉上下文
UIGraphicsEndImageContext();
NSLog(@"圖片合並完成 --- %@", [NSThread currentThread]);
});
// 同時開啟兩個線程 分別下載圖片 會比上面的效率高一點
/**
2016-08-24 16:58:03.785 YJGCDDemo[3467:125346] 圖片1下載完成--- {number = 3, name = (null)}
2016-08-24 16:58:03.978 YJGCDDemo[3467:125349] 圖片2下載完成--- {number = 4, name = (null)}
2016-08-24 16:58:03.978 YJGCDDemo[3467:125303] 顯示圖 --- {number = 1, name = main}
2016-08-24 16:58:03.995 YJGCDDemo[3467:125303] 圖片合並完成 --- {number = 1, name = main}
*/
}
八、線程間通信
// 從子線程回到主線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 執行耗時的異步操作
dispatch_async(dispatch_get_main_queue(), ^{
//回到主線程,執行UI刷新操作
});
});
九、Operation和GCD的對比
優點: 不需要關心線程管理,數據同步的問題;
區別:
1、性能:GCD更接近底層,而NSOperation則更高級抽象,所以GCD在追求性能的底層操作來說,是速度最快的。
2、從異步操作之間的事務性,順序行,依賴關系。GCD需要自己寫更多的代碼來實現,而NSOperationQueue已經內建了這些支持
3、如果異步操作的國學更多的被交互和UI呈現出來,NSOperationQueue會是一個更好的選擇。底層代碼中,任務之間不太互相依賴,而需要更高的並發能力,GCD則更有優勢
十、總結
學了兩天,對gcd有一些的了解,能在項目中使用多線程,不過這邊也要避免很多死鎖的問題,後期有時間再整理出來。四天左右把iOS多線程的幾種方法都整理了下,寫了demo。也算對自己的一個小小總結。
總結兩點:
1、線程運行方式
dispatch_async 異步執行
dispatch_sync 同步執行
dispatch_delay 延遲執行
2、處理任務對象
dispatch_get_main_queue 主線程隊列(UI線程隊列)
dispatch_get_global_queue 並發線程隊列
串行隊列