iOS 多線程(GCD)的使用

1: GCD簡介

什麼是GCD呢? 咱們先來看看百度百科的解釋簡單瞭解下概念:html

引自百度百科:
Grand Central Dispatch(GCD): 是Apple開發的一個多核編程的較新的解決方法。它主要用於優化應用程序以支持多核處理器以及其餘對稱多處理系統。 它是一個在線程池模式的基礎上執行的併發任務。程序員

爲何要用GCD呢?
由於GCD有不少好處, 具體以下:編程

  • GCD 可用於多核的並行運算
  • GCD 會自動利用更多的CPU內核(好比雙核、四核)
  • GCD 會自動管理線程的生命週期(建立線程、調度任務、銷燬線程)
  • 程序員只須要告訴GCD想要執行什麼任務, 不須要編寫任何線程管理代碼

既然GCD有這麼多的好處, 那麼下面咱們就來系統的學習一下GCD的使用方法:安全

2: GCD任務和隊列

學習GCD以前, 先來了解GCD中兩個核心的概念: 任務和隊列。bash

任務: 就是執行操做的意思, 換句話說就是你的線程中執行的那段代碼。 在GCD中是放在block中的。 執行任務有兩種方式:同步執行(sync)和異步執行(async)。
二者的主要區別是:是否等待隊列的任務執行結束, 以及是否具有開啓新線程的能力。session

  • 同步執行(sync):多線程

    • 同步添加任務到指定的隊列中, 在添加的任務執行結束以前, 會一直等待, 直到隊列裏面的任務完成以後再繼續執行。
    • 只能在當前線程中執行任務, 不具有開啓新線程的能力。
  • 異步執行(async):併發

    • 異步添加任務到指定的隊列中, 它不會作任何等待, 能夠繼續執行任務。
    • 能夠在新的線程中執行任務, 具有開啓新線程的能力。

舉個簡單的例子: 你要打電話給小明和小白。app

同步執行就是,你打電話給小明的時候,不能同時打給小白,等到給小明打完了,才能打給小白(等待任務執行結束)。 並且只能用當前的電話(不具有開啓新線程的能力)。異步

而異步執行就是, 你打電話給小明的時候,不等和小明通話結束,還能直接給小白打電話,不用等着和小明通話結束再打(不用等待任務執行結束)。除了當前電話,你還可使用其餘所能使用的電話(具有開啓新線程的能力)。

注意: 異步執行(async)雖然具備開啓新線程的能力,可是並不必定開啓新線程。這跟任務所指定的隊列類型有關(下面會講)。

隊列(Dispatch Queue): 這裏的隊列指執行任務的等待隊列, 即用來存聽任務的隊列。 隊列是一種特殊的線性表, 採用FIFO(先進先出)的原則,即新任務老是被插入到隊列的末尾, 而讀取任務的時候老是從隊列的頭部開始讀取。 每讀取一個任務, 則從隊列中釋放一個任務。隊列的結構可參考下圖:

                 隊列
在GCD中有兩種隊列: 串行隊列和併發隊列。 二者都符合FIFO(先進先出)的原則。 二者的主要區別是: 執行順序不一樣, 以及開啓線程數不一樣。

  • 串行隊列(Serial Dispatch Queue):
    • 每次只有一個任務被執行。讓任務一個接着一個地執行。(只開啓一個線程, 一個任務執行完畢後,再執行下一個任務)
  • 併發隊列(Concurrent Dispatch Queue):
    • 可讓多個任務併發(同時)執行。(能夠開啓多個線程,而且同時執行任務)

注意:併發隊列的併發功能只有在異步(dispatch_async)函數下才有效

二者具體區別以下兩圖所示:

                 串行隊列

                 併發隊列

3: GCD的使用步驟

GCD的使用步驟其實很簡單, 只有兩步:
  1:建立一個隊列(串行隊列或併發隊列)
  2: 將任務追加到任務的等待隊列中,而後系統就會根據任務類型執行任務(同步執行或異步執行)
下邊來看看隊列的建立方法/獲取方法, 以及任務的建立方法。

3.1 隊列的建立方法/獲取方法

  • 可使用dispatch_queue_create來建立隊列, 須要傳入兩個參數, 第一個參數表示隊列的惟一標識符, 用於DEUBG,可爲空, Dispatch Queue的名稱推薦使用應用程序ID這種逆序全程域名;第二個參數用來識別是串行隊列仍是併發隊列。DISPATCH_QUEUE_SERIAL表示串行隊列, DISPATCH_QUEUE_CONCURRENT表示併發隊列
//串行隊列的建立方法
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);
複製代碼
  • 對於串行隊列,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_PRIORITY_DEFAULT。第二個參數暫時沒用, 用0便可。
//全局併發隊列的獲取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
複製代碼

3.2 任務的建立方法

GCD提供了同步執行任務的建立方法dispatch_sync和異步執行任務建立方法dispatch_async。

//同步執行任務建立方法
dispatch_sync(queue, ^{
    //這裏放同步執行任務代碼
});
    
//異步執行任務建立方法
dispatch_async(queue, ^{
    //這裏放異步執行任務代碼
});
複製代碼

雖然使用GCD只需兩步, 可是既然咱們有兩種隊列(串行隊列/併發隊列),兩種任務執行方式(同步執行/異步執行),那麼咱們就有了四種不一樣的組合方式。這四種不一樣的組合方式是:

1: 同步執行 + 併發隊列
2:異步執行 + 併發隊列
3:同步執行 + 串行隊列
4:異步執行 + 串行隊列
複製代碼

實際上,剛纔還說了兩種特殊隊列: 全局併發隊列、主隊列。 全局併發隊列能夠做爲普通併發隊列來使用。可是主隊列由於有點特殊, 因此咱們就又多了兩種組合方式。 這樣就有六種不一樣的組合方式了。

5: 同步執行 + 主隊列
6:異步執行 + 主隊列
複製代碼

那麼這幾種不一樣組合方式各有什麼區別呢?這裏爲了方便, 先上結果, 再來說解。

下邊咱們來分別講講這幾種不一樣的組合方式的使用方法。

4: GCD 的基本使用

先來說講併發隊列的兩種執行方式

4.1 同步執行 + 併發隊列

  • 在當前線程中執行任務, 不會開啓新線程, 執行完一個任務, 再執行下一個任務.
/**
 * 同步執行 + 併發隊列
 * 特色: 在當前線程中執行任務, 不會開啓新線程, 執行完一個任務, 再執行下一個任務
 */
- (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
複製代碼

從同步執行+併發隊列中可看到:

  • 全部任務都是在當前線程(主線程)中執行的,沒有開啓新的線程(同步執行不具有開啓新線程的能力)
  • 全部任務都在打印的syncConcurrent---begin和syncConcurrent -- end之間執行的(同步任務須要等待隊列的任務執行結束)
  • 任務按順序執行的。按順序執行的緣由:雖然併發隊列能夠開啓多個線程,而且同時執行多個任務。可是由於自己不能建立新線程,只有當前線程這一個線程(同步任務不具有開啓新線程的能力),因此也就不存在併發。並且當前線程只有等待當前隊列中正在執行的任務執行完畢以後,才能繼續接着執行下面的操做(同步任務須要等待隊列的任務執行結束)。因此任務只能一個接一個按順序執行,不能同時被執行。

4.2 異步執行 + 併發隊列

  • 能夠開啓多個線程, 任務交替(同時)執行
/**
 * 異步執行 + 併發隊列
 * 特色: 能夠開啓多個線程, 任務交替(同時)執行
 */
- (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)}
複製代碼

在併發隊列+異步執行中能夠看出:

  • 除了當前線程(主線程),系統又開啓了3個線程,而且任務是交替/同時執行的.(異步執行具有開啓新線程的能力.且併發隊列可開啓多個線程,同時執行多個任務)
  • 全部任務是在打印的asyncConcurrent---begin和asyncConcurrent -- end以後才執行的. 說明當前線程沒有等待, 而是直接開啓了新線程, 在新線程中執行任務(異步執行不作等待, 能夠繼續執行任務)

4.3 同步執行 + 串行隊列

  • 不會開啓新線程, 在當前線程執行任務. 任務是串行的, 執行完一個任務, 再執行下一個任務.
/**
 * 同步執行 + 串行隊列
 * 特色: 不會開啓新線程, 在當前線程執行任務. 任務是串行的, 執行完一個任務, 再執行下一個任務.
 */
- (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
複製代碼

從同步執行 + 串行隊列能夠看出:

  • 全部任務都是在當前線程(主線程)中執行的, 並無開啓新的線程(同步執行不具有開啓新線程的能力)
  • 全部任務都在打印的syncSerial---begin和syncSerial -- end之間執行(同步任務須要等待隊列的任務執行結束)
  • 任務是按順序執行的(串行隊列每次只有一個任務被執行, 任務一個接一個按順序執行)

4.4 異步執行 + 串行隊列

  • 會開啓新線程, 可是由於任務是串行的, 執行完一個任務, 再執行下一個任務
/**
 * 異步執行 + 串行隊列
 * 特色: 會開啓新線程, 可是由於任務是串行的, 執行完一個任務, 再執行下一個任務
 */
- (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)}
複製代碼

在異步執行 + 串行隊列中能夠看到:

  • 開啓了一條新線程(異步執行具有開啓新線程的能力, 串行隊列只開啓一個線程)
  • 全部任務是在打印asyncSerial---begin和asyncSerial---end以後纔開始執行的(異步執行不會作任何等待, 能夠繼續執行任務)
  • 任務是按順序執行的(串行隊列每次只有一個任務被執行, 任務一個接一個按順序執行)

下面講講剛纔咱們提到過的特殊隊列: 主隊列

  • 主隊列: GCD自帶的一種特殊的串行隊列
    (1)全部放在主隊列中的任務,都會放到主線程中執行
    (2)可以使用dispatch_get_main_queue()得到主隊列

咱們再來看看主隊列的兩種組合方式.

4.5 同步執行 + 主隊列

  • 同步執行 + 主隊列在不一樣線程中調用結果也是不同的,在主線程中調用會出現死鎖, 而在其餘線程中則不會.

4.5.1 在主線程中調用同步執行 + 主隊列

  • 互相等待卡住不可行
/**
 * 同步執行 + 主隊列
 * 特色(主線程調用):互等卡住不執行
 * 特色(其餘線程調用): 不會開啓新線程, 執行完一個任務, 再執行下一個任務
 */
- (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) 
複製代碼

在同步執行 + 主隊列中能夠發現:

  • 在主線程中使用同步執行 + 主隊列, 追加到主線程的任務1, 任務2, 任務3都再也不執行了, 並且syncMain---end也沒有打印, 在Xcode10上還會崩潰, 這是爲何呢?

這是由於咱們在主線程中執行syncMain方法,至關於把syncMain任務放到了主線程的隊列中. 而同步執行會等待當前隊列中的任務執行完畢, 纔會接着執行. 那麼當咱們把任務1追加到主隊列中, 任務1就在等待主線程處理完syncMain任務. 而syncMain任務須要等待任務1執行完畢, 才能接着執行.

那麼, 如今的狀況就是syncMain任務和任務1都在等對方執行完畢. 這樣你們互相等待, 因此就卡住了, 因此咱們的任務執行不了, 並且syncMain---end也沒有打印.

若是不在主線程中調用, 而在其餘線程中調用會如何呢?

4.5.2 在其餘線程中調用同步執行+ 主隊列

  • 不會開啓新線程, 執行完一個任務, 再執行下一個任務
// 使用 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---begin和syncMain---end之間執行(同步任務須要等待隊列的任務執行結束)
  • 任務是按順序執行的(主隊列是串行隊列, 每次只有一個任務執行, 任務一個接一個按順序執行).

爲何如今就不會卡住了呢? 由於syncMain任務放到了其餘線程裏, 而任務1, 任務2, 任務3都追加到了主隊了中, 這三個任務都會在主線程中執行. syncMain任務在其餘線程中執行到追加任務1到主隊列中, 由於主隊列如今沒有正在執行的任務, 因此, 會直接執行主隊列的任務1, 等任務1執行完畢, 再接着執行任務2, 任務3. 因此這裏不會卡住線程.

4.6 異步執行 + 主隊列

  • 只在主線程中執行任務, 執行完一個任務, 再執行下一個任務.
/**
 * 異步執行 + 主隊列
 * 特色: 只在主線程中執行任務, 執行完一個任務, 再執行下一個任務
 */
- (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}
複製代碼

在異步執行 + 主隊列中能夠看到:

  • 全部任務都是在當前線程(主線程)中執行的, 並無開啓新的線程(雖然異步執行具有開啓線程的能力, 但由於是主隊列, 因此全部任務都在主線程中).
  • 全部任務是在打印的asyncMain---begin和asyncMain---end以後纔開始執行的(異步執行不會作任何等待, 能夠繼續執行任務).
  • 任務是按順序執行的(由於主隊列是串行隊列, 每次只有一個任務被執行, 任務一個接一個按順序執行).

5: GCD 線程間的通訊

在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}
複製代碼
  • 能夠看到在其餘線程中先執行任務, 執行完了以後回到主線程執行主線程的相應操做.

6: GCD的其餘方法

6.1 GCD柵欄方法: dispatch_barrier_async

  • 咱們有時須要異步執行兩組操做, 並且第一組操做執行完以後, 才能開始執行第二組操做. 這樣咱們就須要一個至關於柵欄同樣的一個方法將兩組異步執行的操做給分割起來, 固然這裏的操做組裏能夠包含一個或多個任務. 這就須要用到dispatch_barrier_async方法在兩個操做組間造成柵欄.
    dispatch_barrier_async函數會等待前邊追加的併發隊列中的任務所有執行完畢以後, 再將指定的任務追加到該異步隊列中. 而後在dispatch_barrier_async函數追加的任務執行完畢以後, 異步隊列才恢復爲通常動做, 接着追加任務到該異步隊列並開始執行. 具體以下圖所示:
/**
 * 柵欄方法: 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執行結果中能夠看出:

  • 在執行完柵欄前面的操做以後, 才執行柵欄操做, 最後再執行柵欄後邊的操做.

6.2 GCD延時執行方法: dispatch_after

咱們常常會遇到這樣的需求: 在指定時間(例如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}

6.3 GCD 一次性代碼(只執行一次): dispatch_once

  • 咱們在建立單例、或者有整個程序運行過程當中只執行一次的代碼時,咱們就用到了GCD的dispatch_once函數.使用dispatch_once函數能保證某段代碼在程序運行過程當中只執行1次,而且即便在多線程的環境下,dispatch_once也能夠保證線程安全.
/**
 * 一次性代碼(只執行一次): 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,最終都只會執行一次;

6.4 GCD快速迭代方法:dispatch_apply

  • 一般咱們會用for循環遍歷,可是GCD給咱們提供了快速迭代的函數dispatch_apply. dispatch_apply按照指定的次數將指定的任務追加到指定的隊列中, 並等待所有隊列執行結束.
    若是是在串行隊列中使用dispatch_apply, 那麼就和for循環同樣, 按順序同步執行. 可這樣就體現不出快速迭代的意義了.
    咱們能夠利用併發隊列進行異步執行. 好比說遍歷0~5這6個數字, for循環的作法是每次取出一個元素, 逐個遍歷. dispatch_apply能夠在多個線程中同時(異步)遍歷多個數字.
    還有一點, 不管是在串行隊列, 仍是異步隊列中, dispatch_apply都會等待所有任務執行完畢, 這點就像是同步操做, 也像是隊列組中的dispatch_group_wait方法.
/**
 快速迭代方法 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函數會等待所有任務執行完畢.

6.5 GCD隊列組: dispatch_group

有時候咱們會有這樣的需求: 分別異步執行2個耗時任務, 而後當2個耗時任務都執行完畢後再回到主線程執行任務. 這時候咱們能夠用到GCD的隊列組.

  • 調用隊列組的dispatch_group_async先把任務放到隊列中, 而後將隊列放入隊列組中. 或者使用隊列組的dispatch_group_enter, dispatch_group_leave組合來實現dispatch_group_async;
  • 調用隊列組的dispatch_group_notify回到指定線程執行任務. 或者使用dispatch_group_wait回到當前線程繼續向下執行(會阻塞當前線程)

6.5.1 dispatch_group_notify

  • 監聽group中任務的完成狀態, 當全部的任務都執行完成後, 追加任務到group中, 並執行任務.
/**
 隊列組 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中的任務.

6.5.2 dispatch_group_wait

  • 暫停當前線程(阻塞當前線程), 等待指定的group中的任務執行完成後, 纔會往下繼續執行.
/**
 隊列組 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_wait以前全部的任務都執行完成以後, 纔會執行dispatch_group_wait以後的操做.
  • 使用dispatch_group_wait會阻塞當前線程(從輸出結果就能夠看出)
  • dispatch_group_wait的功能相似於柵欄方法dispatch_barrier_async, 不管有幾個併發隊列, dispatch_group_wait以前的方法沒有執行完,就不會執行以後的隊列.

6.5.3 dispatch_group_enter, dispatch_group_leave

  • dispatch_group_enter 標誌着一個任務追加到group, 執行一次, 至關於group中未執行完畢的任務數+1;
  • dispatch_group_leave 標誌着一個任務離開了group, 執行一次, 至關於group中未執行完畢的任務書-1;
  • 當group中未執行完畢任務數爲0的時候, 纔會使dispatch_group_wait解除阻塞, 以及執行追加到dispatch_group_notify中的任務.
/**
 隊列組 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.

6.6 GCD信號量: dispatch_semaphore

GCD中的信號量是指Dispatch Semaphore, 是持有計數的信號. 相似於太高速路收費站的欄杆. 能夠經過時, 打開欄杆, 不能夠經過時, 關閉欄杆. 在Dispatch Semaphore中, 使用計數來完成這個功能, 計數小於0時等待, 不可經過.計數爲0或大於0時, 計數減1且不等待, 可經過.
Dispatch Semaphore 提供了三個函數

  • dispatch_semaphore_create: 建立一個 Semaphore 並初始化信號的總量
  • dispatch_semaphore_signal: 發送一個信號, 讓信號總量加1
  • dispatch_semaphore_wait: 可使總信號量減1, 信號總量小於0時就會一直等待(阻塞所在線程), 不然就能夠正常執行.

注意: 信號量的使用前提是: 想清楚你須要處理哪一個線程等待(阻塞), 又要哪一個線程繼續執行, 而後使用信號量.

Dispatch Semaphore 在實際開發中主要用於:

  • 保持線程同步, 將異步執行任務轉換爲同步執行任務.
  • 保證線程安全, 爲線程加鎖.

6.6.1 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實現線程同步的代碼能夠看到:

  • semaphore---end 是在執行完 number = 100以後纔打印的. 並且輸出結果number爲100, 這是由於異步執行不會作任何等待, 能夠繼續執行任務.
    執行順序以下:
    • semaphore 初始建立時計數爲0.
    • 異步執行將任務1追加到隊列以後, 不作等待, 接着執行dispatch_semaphore_wait方法, semaphore減1, 此時semaphore == -1, 當前線程進入等待狀態.
    • 而後, 異步任務1開始執行, 任務1執行到dispatch_semaphore_signal以後, 總信號量加1, 此時semaphore == 0, 正在被阻塞的線程(主線程)恢復繼續執行.
    • 最後打印semaphore---end, number = 100.

這樣就實現了線程同步, 將異步執行任務轉換爲同步執行任務.

6.6.2 Dispatch Semaphore 線程安全和線程同步(爲線程加鎖)

線程安全: 若是你的代碼所在的進程中有多個線程在同時運行, 而這些線程可能會同時運行這段代碼. 若是每次運行結果和單線程運行的結果是同樣的, 並且其餘的變量的值也和預期的是同樣的, 就是線程安全的.
若每一個線程中對全局變量、靜態變量只有讀操做, 而無寫操做, 通常來講, 這個全局變量是線程安全的; 如有多個線程同時執行寫操做(更改變量), 通常都須要考慮線程同步, 不然的話就可能影響線程安全。

線程同步: 可理解爲線程A和線程B一塊配合, A執行到必定程度時要依靠線程B的某個結果, 因而停下來, 示意B運行; B依言執行,再將結果給A; A再繼續操做。

舉個簡單的例子就是: 兩我的在一塊兒聊天。兩我的不能同時說話, 避免聽不清(操做衝突)。等一我的說完(一個線程結束操做), 另外一個再說(另外一個線程再開始操做)。

下面,咱們模擬火車票售賣的方式, 實現NSThread線程安全和解決線程同步問題。

場景: 總共有50張火車票, 有兩個售賣火車票的窗口, 一個是北京火車票售賣窗口, 另外一個是上海火車票售賣窗口。 兩個窗口同時售賣火車票, 賣完爲止。

6.6.2.1 非線程安全(不適用semaphore)

先來看看不考慮線程安全的代碼:

/**
 * 非線程安全: 不使用 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的狀況下, 獲得的票數是錯亂的, 這樣顯然不符合咱們的需求, 因此咱們須要考慮線程安全的問題。

6.6.2.2 線程安全 (使用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機制以後, 獲得的票數是正確的, 沒有出現混亂的狀況。 咱們也就解決了多個線程同步的問題。

原文: www.cnblogs.com/yxl-151217/…

相關文章
相關標籤/搜索