什麼是GCD呢? 咱們先來看看百度百科的解釋簡單瞭解下概念:html
引自百度百科:
Grand Central Dispatch(GCD): 是Apple開發的一個多核編程的較新的解決方法。它主要用於優化應用程序以支持多核處理器以及其餘對稱多處理系統。 它是一個在線程池模式的基礎上執行的併發任務。程序員
爲何要用GCD呢?
由於GCD有不少好處, 具體以下:編程
既然GCD有這麼多的好處, 那麼下面咱們就來系統的學習一下GCD的使用方法:安全
學習GCD以前, 先來了解GCD中兩個核心的概念: 任務和隊列。bash
任務: 就是執行操做的意思, 換句話說就是你的線程中執行的那段代碼。 在GCD中是放在block中的。 執行任務有兩種方式:同步執行(sync)和異步執行(async)。
二者的主要區別是:是否等待隊列的任務執行結束, 以及是否具有開啓新線程的能力。session
同步執行(sync):多線程
異步執行(async):併發
舉個簡單的例子: 你要打電話給小明和小白。app
同步執行就是,你打電話給小明的時候,不能同時打給小白,等到給小明打完了,才能打給小白(等待任務執行結束)。 並且只能用當前的電話(不具有開啓新線程的能力)。異步
而異步執行就是, 你打電話給小明的時候,不等和小明通話結束,還能直接給小白打電話,不用等着和小明通話結束再打(不用等待任務執行結束)。除了當前電話,你還可使用其餘所能使用的電話(具有開啓新線程的能力)。
注意: 異步執行(async)雖然具備開啓新線程的能力,可是並不必定開啓新線程。這跟任務所指定的隊列類型有關(下面會講)。
隊列(Dispatch Queue): 這裏的隊列指執行任務的等待隊列, 即用來存聽任務的隊列。 隊列是一種特殊的線性表, 採用FIFO(先進先出)的原則,即新任務老是被插入到隊列的末尾, 而讀取任務的時候老是從隊列的頭部開始讀取。 每讀取一個任務, 則從隊列中釋放一個任務。隊列的結構可參考下圖:
注意:併發隊列的併發功能只有在異步(dispatch_async)函數下才有效
二者具體區別以下兩圖所示:
串行隊列GCD的使用步驟其實很簡單, 只有兩步:
1:建立一個隊列(串行隊列或併發隊列)
2: 將任務追加到任務的等待隊列中,而後系統就會根據任務類型執行任務(同步執行或異步執行)
下邊來看看隊列的建立方法/獲取方法, 以及任務的建立方法。
//串行隊列的建立方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
//併發隊列的建立方法
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
複製代碼
//主隊列的獲取方法
dispatch_queue_t queue = dispatch_get_main_queue();
複製代碼
//全局併發隊列的獲取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
複製代碼
GCD提供了同步執行任務的建立方法dispatch_sync和異步執行任務建立方法dispatch_async。
//同步執行任務建立方法
dispatch_sync(queue, ^{
//這裏放同步執行任務代碼
});
//異步執行任務建立方法
dispatch_async(queue, ^{
//這裏放異步執行任務代碼
});
複製代碼
雖然使用GCD只需兩步, 可是既然咱們有兩種隊列(串行隊列/併發隊列),兩種任務執行方式(同步執行/異步執行),那麼咱們就有了四種不一樣的組合方式。這四種不一樣的組合方式是:
1: 同步執行 + 併發隊列
2:異步執行 + 併發隊列
3:同步執行 + 串行隊列
4:異步執行 + 串行隊列
複製代碼
實際上,剛纔還說了兩種特殊隊列: 全局併發隊列、主隊列。 全局併發隊列能夠做爲普通併發隊列來使用。可是主隊列由於有點特殊, 因此咱們就又多了兩種組合方式。 這樣就有六種不一樣的組合方式了。
5: 同步執行 + 主隊列
6:異步執行 + 主隊列
複製代碼
那麼這幾種不一樣組合方式各有什麼區別呢?這裏爲了方便, 先上結果, 再來說解。
下邊咱們來分別講講這幾種不一樣的組合方式的使用方法。先來說講併發隊列的兩種執行方式
/**
* 同步執行 + 併發隊列
* 特色: 在當前線程中執行任務, 不會開啓新線程, 執行完一個任務, 再執行下一個任務
*/
- (void)syncConcurrent {
//打印當前線程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.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");
}
輸出:
currentThread = <NSThread: 0x600003aca940>{number = 1, name = main}
syncConcurrent---begin
1---<NSThread: 0x600003aca940>{number = 1, name = main}
1---<NSThread: 0x600003aca940>{number = 1, name = main}
2---<NSThread: 0x600003aca940>{number = 1, name = main}
2---<NSThread: 0x600003aca940>{number = 1, name = main}
3---<NSThread: 0x600003aca940>{number = 1, name = main}
3---<NSThread: 0x600003aca940>{number = 1, name = main}
syncConcurrent -- end
複製代碼
從同步執行+併發隊列中可看到:
/**
* 異步執行 + 併發隊列
* 特色: 能夠開啓多個線程, 任務交替(同時)執行
*/
- (void)asyncConcurrent {
//打印當前線程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.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");
}
輸出結果:
currentThread = <NSThread: 0x600000f494c0>{number = 1, name = main}
asyncConcurrent---begin
asyncConcurrent -- end
3---<NSThread: 0x600000fda980>{number = 3, name = (null)}
2---<NSThread: 0x600000fdf700>{number = 5, name = (null)}
1---<NSThread: 0x600000fc5ec0>{number = 4, name = (null)}
3---<NSThread: 0x600000fda980>{number = 3, name = (null)}
2---<NSThread: 0x600000fdf700>{number = 5, name = (null)}
1---<NSThread: 0x600000fc5ec0>{number = 4, name = (null)}
複製代碼
在併發隊列+異步執行中能夠看出:
/**
* 同步執行 + 串行隊列
* 特色: 不會開啓新線程, 在當前線程執行任務. 任務是串行的, 執行完一個任務, 再執行下一個任務.
*/
- (void)syncSerial {
//打印當前線程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"syncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.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");
}
輸出結果:
currentThread = <NSThread: 0x6000017394c0>{number = 1, name = main}
syncSerial---begin
1---<NSThread: 0x6000017394c0>{number = 1, name = main}
1---<NSThread: 0x6000017394c0>{number = 1, name = main}
2---<NSThread: 0x6000017394c0>{number = 1, name = main}
2---<NSThread: 0x6000017394c0>{number = 1, name = main}
3---<NSThread: 0x6000017394c0>{number = 1, name = main}
3---<NSThread: 0x6000017394c0>{number = 1, name = main}
syncSerial -- end
複製代碼
從同步執行 + 串行隊列能夠看出:
/**
* 異步執行 + 串行隊列
* 特色: 會開啓新線程, 可是由於任務是串行的, 執行完一個任務, 再執行下一個任務
*/
- (void)asyncSerial {
//打印當前線程
NSLog(@"currentThread = %@",NSThread.currentThread);
NSLog(@"asyncSerial---begin");
dispatch_queue_t queue = dispatch_queue_create("net.bujige.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");
}
輸出結果:
currentThread = <NSThread: 0x60000357e8c0>{number = 1, name = main}
asyncSerial---begin
asyncSerial---end
1---<NSThread: 0x600003573540>{number = 3, name = (null)}
1---<NSThread: 0x600003573540>{number = 3, name = (null)}
2---<NSThread: 0x600003573540>{number = 3, name = (null)}
2---<NSThread: 0x600003573540>{number = 3, name = (null)}
3---<NSThread: 0x600003573540>{number = 3, name = (null)}
3---<NSThread: 0x600003573540>{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");
}
輸出結果:
currentThread = <NSThread: 0x6000031c28c0>{number = 1, name = main}
syncMain---begin
(lldb)
複製代碼
在同步執行 + 主隊列中能夠發現:
這是由於咱們在主線程中執行syncMain方法,至關於把syncMain任務放到了主線程的隊列中. 而同步執行會等待當前隊列中的任務執行完畢, 纔會接着執行. 那麼當咱們把任務1追加到主隊列中, 任務1就在等待主線程處理完syncMain任務. 而syncMain任務須要等待任務1執行完畢, 才能接着執行.
那麼, 如今的狀況就是syncMain任務和任務1都在等對方執行完畢. 這樣你們互相等待, 因此就卡住了, 因此咱們的任務執行不了, 並且syncMain---end也沒有打印.
若是不在主線程中調用, 而在其餘線程中調用會如何呢?
// 使用 NSThread 的 detachNewThreadSelector 方法會建立線程, 並自動啓動線程執行selector 任務
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
輸出結果:
currentThread = <NSThread: 0x600003fe09c0>{number = 3, name = (null)}
syncMain---begin
1---<NSThread: 0x600003f65480>{number = 1, name = main}
1---<NSThread: 0x600003f65480>{number = 1, name = main}
2---<NSThread: 0x600003f65480>{number = 1, name = main}
2---<NSThread: 0x600003f65480>{number = 1, name = main}
3---<NSThread: 0x600003f65480>{number = 1, name = main}
3---<NSThread: 0x600003f65480>{number = 1, name = main}
syncMain---end
複製代碼
在其餘線程中使用同步執行 + 主隊列可看到:
爲何如今就不會卡住了呢? 由於syncMain任務放到了其餘線程裏, 而任務1, 任務2, 任務3都追加到了主隊了中, 這三個任務都會在主線程中執行. syncMain任務在其餘線程中執行到追加任務1到主隊列中, 由於主隊列如今沒有正在執行的任務, 因此, 會直接執行主隊列的任務1, 等任務1執行完畢, 再接着執行任務2, 任務3. 因此這裏不會卡住線程.
/**
* 異步執行 + 主隊列
* 特色: 只在主線程中執行任務, 執行完一個任務, 再執行下一個任務
*/
- (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");
}
輸出結果:
currentThread = <NSThread: 0x6000017dd4c0>{number = 1, name = main}
asyncMain---begin
asyncMain---end
1---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
1---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
2---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
2---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
3---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
3---<NSThread: 0x6000017dd4c0>{number = 1, name = main}
複製代碼
在異步執行 + 主隊列中能夠看到:
在iOS開發過程當中, 咱們通常在主線程裏邊進行UI刷新, 例如: 點擊, 滾動, 拖拽等事件. 咱們一般把一些耗時的操做放在其餘線程, 好比說圖片下載, 文件上傳等耗時操做, 而當咱們有時候在其餘線程完成了耗時操做時, 須要回到主線程, 那麼就用到了線程之間的通信.
/**
* 線程間通訊
*/
- (void)communication {
NSLog(@"currentThread---%@",NSThread.currentThread);//打印當前線程
//獲取全局併發隊列
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:4]; //模擬耗時操做
NSLog(@"2---%@",NSThread.currentThread); //打印當前線程
});
}
輸出結果:
currentThread---<NSThread: 0x60000137c980>{number = 1, name = main}
不符合標磚
1---<NSThread: 0x6000013e4d00>{number = 3, name = (null)}
1---<NSThread: 0x6000013e4d00>{number = 3, name = (null)}
2---<NSThread: 0x60000137c980>{number = 1, name = main}
複製代碼
/**
* 柵欄方法: dispatch_barrier_async
*/
- (void)barrier {
NSLog(@"currentThread---%@",NSThread.currentThread);//打印當前線程
dispatch_queue_t queue = dispatch_queue_create("net.bujige.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_barrier_async(queue, ^{
//追加任務 barrier
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"barrier---%@",NSThread.currentThread); //打印當前線程
}
});
dispatch_async(queue, ^{
//追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"3---%@",NSThread.currentThread);
}
});
dispatch_async(queue, ^{
//追加任務4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"4---%@",NSThread.currentThread);
}
});
}
輸出結果:
currentThread---<NSThread: 0x6000002ec080>{number = 1, name = main}
2---<NSThread: 0x60000028ba00>{number = 4, name = (null)}
1---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
1---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
2---<NSThread: 0x60000028ba00>{number = 4, name = (null)}
barrier---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
barrier---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
3---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
4---<NSThread: 0x600000283f80>{number = 5, name = (null)}
4---<NSThread: 0x600000283f80>{number = 5, name = (null)}
3---<NSThread: 0x6000002825c0>{number = 3, name = (null)}
複製代碼
在dispatch_barrier_async執行結果中能夠看出:
咱們常常會遇到這樣的需求: 在指定時間(例如3秒)以後執行某個任務. 能夠用GCD的dispatch_after函數來實現.
須要注意的是: dispatch_after函數並非在指定時間以後纔開始執行處理, 而是在指定時間以後將任務追加到主隊列中. 嚴格來講, 這個時間並非絕對準確的, 但要想大體延遲執行任務, dispatch_after函數是頗有效的.
/**
* 延時執行任務: dispatch_after
*/
- (void)after {
NSLog(@"currentThread---%@",NSThread.currentThread);//打印當前線程
NSLog(@"after---begin");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//2.0 秒後異步追加任務代碼到主隊列, 並開始執行
NSLog(@"after---%@",NSThread.currentThread); //打印當前線程
});
}
輸出結果:
17:51:02.998177+0800 currentThread---<NSThread: 0x600002cacd40>{number = 1, name = main}
17:51:02.998384+0800 after---begin
17:51:04.998680+0800 after---<NSThread: 0x600002cacd40>{number = 1, name = main}
複製代碼
能夠看出: 在打印after---begin以後大約2.0秒的時間, 打印了after---<NSThread: 0x600002cacd40>{number = 1, name = main}
/**
* 一次性代碼(只執行一次): dispatch_once
*/
- (void)once {
for (int i = 0; i < 10; i++) {
NSLog(@"第%d次執行GCD一次性代碼",i);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//只執行1次的代碼(這裏面默認是線程安全的)
NSLog(@"只執行1次的代碼");
});
}
}
輸出結果:
第0次執行GCD一次性代碼
只執行1次的代碼
第1次執行GCD一次性代碼
第2次執行GCD一次性代碼
第3次執行GCD一次性代碼
第4次執行GCD一次性代碼
第5次執行GCD一次性代碼
第6次執行GCD一次性代碼
第7次執行GCD一次性代碼
第8次執行GCD一次性代碼
第9次執行GCD一次性代碼
複製代碼
從輸出結果能夠看出, 不管執行多少次dispatch_once,最終都只會執行一次;
/**
快速迭代方法 dispatch_apply
*/
- (void)apply {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, NSThread.currentThread);
});
NSLog(@"apply-end");
}
輸出結果:
apply---begin
2---<NSThread: 0x60000198ccc0>{number = 5, name = (null)}
4---<NSThread: 0x600001981000>{number = 6, name = (null)}
3---<NSThread: 0x6000019f14c0>{number = 1, name = main}
0---<NSThread: 0x6000019ab380>{number = 3, name = (null)}
1---<NSThread: 0x60000198a080>{number = 4, name = (null)}
5---<NSThread: 0x6000019ab4c0>{number = 7, name = (null)}
apply-end
複製代碼
由於是在併發隊列中異步執行任務, 因此各個任務的執行時間長短不定, 最後結束順序也不定. 可是apply-end必定在最後執行. 這是由於dispatch_apply函數會等待所有任務執行完畢.
有時候咱們會有這樣的需求: 分別異步執行2個耗時任務, 而後當2個耗時任務都執行完畢後再回到主線程執行任務. 這時候咱們能夠用到GCD的隊列組.
/**
隊列組 dispatch_group_notify
*/
- (void)groupNotify {
//打印當前線程
NSLog(@"currentThread---%@",NSThread.currentThread);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//追加任務1
for (int i = 0 ; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"1---%@",NSThread.currentThread); //打印當前線程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"2---%@",[NSThread currentThread]); //打印當前線程
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//等前面的異步任務1, 任務2都執行完畢後, 回到主線程執行下邊的任務
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"3---%@",[NSThread currentThread]); //打印當前線程
}
NSLog(@"group---end");
});
}
輸出結果:
currentThread---<NSThread: 0x60000202e8c0>{number = 1, name = main}
group---begin
2---<NSThread: 0x6000020a9040>{number = 4, name = (null)}
1---<NSThread: 0x6000020a89c0>{number = 3, name = (null)}
2---<NSThread: 0x6000020a9040>{number = 4, name = (null)}
1---<NSThread: 0x6000020a89c0>{number = 3, name = (null)}
3---<NSThread: 0x60000202e8c0>{number = 1, name = main}
3---<NSThread: 0x60000202e8c0>{number = 1, name = main}
group---end
複製代碼
從dispatch_group_notify相關代碼運行輸出結果能夠看出:
當全部任務都執行完成以後, 才執行dispatch_group_notify block中的任務.
/**
隊列組 dispatch_group_wait
*/
- (void)groupWait {
NSLog(@"currentThread---%@", NSThread.currentThread);
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"1---%@",NSThread.currentThread); //打印當前線程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"2---%@",NSThread.currentThread); //打印當前線程
}
});
// 等待上面的任務所有完成後, 會往下繼續執行(會阻塞當前線程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務3
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"3---%@",NSThread.currentThread); //打印當前線程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務4
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"4---%@",NSThread.currentThread); //打印當前線程
}
});
}
輸出結果:
currentThread---<NSThread: 0x6000018e94c0>{number = 1, name = main}
group---begin
2---<NSThread: 0x600001895b80>{number = 3, name = (null)}
1---<NSThread: 0x60000186d700>{number = 4, name = (null)}
1---<NSThread: 0x60000186d700>{number = 4, name = (null)}
2---<NSThread: 0x600001895b80>{number = 3, name = (null)}
group---end
4---<NSThread: 0x60000186d700>{number = 4, name = (null)}
3---<NSThread: 0x600001895b80>{number = 3, name = (null)}
3---<NSThread: 0x600001895b80>{number = 3, name = (null)}
4---<NSThread: 0x60000186d700>{number = 4, name = (null)}
複製代碼
從dispatch_group_wait相關代碼運行輸出結果能夠看出:
/**
隊列組 dispatch_group_enter, dispatch_group_leave
*/
- (void)groupEnterAndLeave {
NSLog(@"currentThread---%@",NSThread.currentThread);//打印當前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"1---%@",NSThread.currentThread); //打印當前線程
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"2---%@",NSThread.currentThread); //打印當前線程
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操做都執行完畢後, 回到主線程
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"3---%@",NSThread.currentThread);
}
NSLog(@"group-end");
});
}
輸出結果:
currentThread---<NSThread: 0x600000326c80>{number = 1, name = main}
group---begin
1---<NSThread: 0x60000035ae40>{number = 4, name = (null)}
2---<NSThread: 0x6000003a1840>{number = 3, name = (null)}
1---<NSThread: 0x60000035ae40>{number = 4, name = (null)}
2---<NSThread: 0x6000003a1840>{number = 3, name = (null)}
3---<NSThread: 0x600000326c80>{number = 1, name = main}
3---<NSThread: 0x600000326c80>{number = 1, name = main}
group-end
複製代碼
從dispatch_group_enter, dispatch_group_leave相關代碼運行結果中能夠看出: 當全部任務執行完成以後,才執行dispatch_group_notify中的任務. 這裏的dispatch_group_enter, dispatch_group_leave組合, 其實等同於dispatch_group_async.
GCD中的信號量是指Dispatch Semaphore, 是持有計數的信號. 相似於太高速路收費站的欄杆. 能夠經過時, 打開欄杆, 不能夠經過時, 關閉欄杆. 在Dispatch Semaphore中, 使用計數來完成這個功能, 計數小於0時等待, 不可經過.計數爲0或大於0時, 計數減1且不等待, 可經過.
Dispatch Semaphore 提供了三個函數
注意: 信號量的使用前提是: 想清楚你須要處理哪一個線程等待(阻塞), 又要哪一個線程繼續執行, 而後使用信號量.
Dispatch Semaphore 在實際開發中主要用於:
咱們在開發中, 會遇到這樣的需求: 異步執行耗時任務, 並使用異步執行的結果進行一些額外的操做. 換句話說, 至關於, 將異步執行任務轉換爲同步執行任務. 好比說: AFNetworking中AFURLSessionManager.m裏面的tasksForKeyPath: 方法. 經過引入信號量的方式, 等待異步執行任務結果, 獲取到tasks, 而後再返回該tasks.
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
__block NSArray *tasks = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
tasks = dataTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
tasks = uploadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
tasks = downloadTasks;
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
}
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return tasks;
}
複製代碼
下面, 咱們來利用Dispatch Semaphore實現線程同步
/**
semaphore 線程同步
*/
- (void)semaphoreSync {
NSLog(@"currentThread---%@",NSThread.currentThread); //打印當前線程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);//semaphore初始建立時計數爲0
__block int number = 0;
dispatch_async(queue, ^{
// 追加任務1
[NSThread sleepForTimeInterval:2]; //模擬耗時操做
NSLog(@"1---%@",NSThread.currentThread);
number = 100;
dispatch_semaphore_signal(semaphore);//總信號量+1, 此時semaphore == 0, 正在被阻塞的線程(主線程)恢復繼續執行
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //semaphore減1, 此時semaphore == -1, 當前線程進入等待狀態
NSLog(@"semaphore---end, number = %zd",(long)number);
}
輸出結果:
currentThread---<NSThread: 0x600001d394c0>{number = 1, name = main}
semaphore---begin
1---<NSThread: 0x600001d46480>{number = 3, name = (null)}
semaphore---end, number = 100
複製代碼
從Dispatch Semaphore實現線程同步的代碼能夠看到:
這樣就實現了線程同步, 將異步執行任務轉換爲同步執行任務.
線程安全: 若是你的代碼所在的進程中有多個線程在同時運行, 而這些線程可能會同時運行這段代碼. 若是每次運行結果和單線程運行的結果是同樣的, 並且其餘的變量的值也和預期的是同樣的, 就是線程安全的.
若每一個線程中對全局變量、靜態變量只有讀操做, 而無寫操做, 通常來講, 這個全局變量是線程安全的; 如有多個線程同時執行寫操做(更改變量), 通常都須要考慮線程同步, 不然的話就可能影響線程安全。
線程同步: 可理解爲線程A和線程B一塊配合, A執行到必定程度時要依靠線程B的某個結果, 因而停下來, 示意B運行; B依言執行,再將結果給A; A再繼續操做。
舉個簡單的例子就是: 兩我的在一塊兒聊天。兩我的不能同時說話, 避免聽不清(操做衝突)。等一我的說完(一個線程結束操做), 另外一個再說(另外一個線程再開始操做)。
下面,咱們模擬火車票售賣的方式, 實現NSThread線程安全和解決線程同步問題。
場景: 總共有50張火車票, 有兩個售賣火車票的窗口, 一個是北京火車票售賣窗口, 另外一個是上海火車票售賣窗口。 兩個窗口同時售賣火車票, 賣完爲止。
先來看看不考慮線程安全的代碼:
/**
* 非線程安全: 不使用 semaphore
* 初始化火車票數量, 賣票窗口(非線程安全), 並開始賣票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",NSThread.currentThread); //打印當前線程
NSLog(@"semaphore--begin");
self.ticketSurplusCount = 50;
//queue1 表明北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
//queue2 表明上海火車票售賣窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof (self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
售賣火車票(非線程安全)
*/
- (void)saleTicketNotSafe {
while (1) {
if (self.ticketSurplusCount > 0) { //若是還有票, 繼續售票
self.ticketSurplusCount--;
NSLog(@"剩餘票數: %d 窗口: %@",self.ticketSurplusCount, NSThread.currentThread);
[NSThread sleepForTimeInterval:0.2];
}
else { //若是已賣完, 關閉售票窗口
NSLog(@"全部火車票均已售完");
break;
}
}
}
輸出結果:
currentThread---<NSThread: 0x60000083cf80>{number = 1, name = main}
semaphore--begin
剩餘票數: 48 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩餘票數: 49 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩餘票數: 46 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩餘票數: 47 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩餘票數: 45 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
...
剩餘票數: 5 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩餘票數: 4 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩餘票數: 3 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
剩餘票數: 1 窗口: <NSThread: 0x600000844140>{number = 3, name = (null)}
剩餘票數: 2 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
全部火車票均已售完
剩餘票數: 0 窗口: <NSThread: 0x60000084c340>{number = 4, name = (null)}
全部火車票均已售完
複製代碼
能夠看到在不考慮線程安全, 不適用semaphore的狀況下, 獲得的票數是錯亂的, 這樣顯然不符合咱們的需求, 因此咱們須要考慮線程安全的問題。
考慮線程安全的代碼:
/**
* 線程安全: 使用 semaphore 加鎖
* 初始化火車票數量, 賣票窗口(線程安全), 並開始賣票
*/
- (void)initTicketStatusNotSave {
NSLog(@"currentThread---%@",NSThread.currentThread); //打印當前線程
NSLog(@"semaphore--begin");
self.semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
//queue1 表明北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
//queue2 表明上海火車票售賣窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof (self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketNotSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketNotSafe];
});
}
/**
售賣火車票(線程安全)
*/
- (void)saleTicketNotSafe {
while (1) {
//至關於加鎖
dispatch_semaphore_wait(self.semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) { //若是還有票, 繼續售票
self.ticketSurplusCount--;
NSLog(@"剩餘票數: %d 窗口: %@",self.ticketSurplusCount, NSThread.currentThread);
[NSThread sleepForTimeInterval:0.2];
}
else { //若是已賣完, 關閉售票窗口
NSLog(@"全部火車票均已售完");
//至關於解鎖
dispatch_semaphore_signal(self.semaphoreLock);
break;
}
//至關於解鎖
dispatch_semaphore_signal(self.semaphoreLock);
}
}
輸出結果:
currentThread---<NSThread: 0x60000317e8c0>{number = 1, name = main}
semaphore--begin
剩餘票數: 49 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩餘票數: 48 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩餘票數: 47 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩餘票數: 46 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩餘票數: 45 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
...
剩餘票數: 5 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩餘票數: 4 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩餘票數: 3 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩餘票數: 2 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
剩餘票數: 1 窗口: <NSThread: 0x6000031115c0>{number = 3, name = (null)}
剩餘票數: 0 窗口: <NSThread: 0x600003113240>{number = 4, name = (null)}
全部火車票均已售完
全部火車票均已售完
複製代碼
能夠看出, 在考慮了線程安全的狀況下, 使用dispatch_semaphore機制以後, 獲得的票數是正確的, 沒有出現混亂的狀況。 咱們也就解決了多個線程同步的問題。