iOS 多線程-GCD

本文內容
任務、隊列的概念、創建方式
任務 + 隊列的6種組合的執行方式
線程間如何通信
dispatch_once、dispatch_after、dispatch_apply(快速迭代)、dispatch_barrier(柵欄函數)、dispatch_group(隊列組)、dispatch_semaphore(信號量)如何實現線程安全與線程同步
iOS多線程demo地址

上文說到iOS 多線程- pThread和NSThread
這篇文章來講講GCD

GCD ??????????的優點

  1. 可用于多核的并行運算
  2. 會自動利用更多的CPU內核
  3. 自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
  4. 只用關注執行什么任務,不用編寫任何線程管理代碼

1.任務

任務:執行的操作,放在block中的代碼

執行任務的方式有兩種,主要區別是:是否等待隊列中的任務執行結束,是否具備開啟新線程的能力

  1. 同步執行(sync):同步添加當前任務到指定的隊列中,在隊列中的任務全部結束之前,會一直等待,直到隊列中的任務全部完成后,才開始下一個任務,只能在當前線程中執行任務,不具備開啟線程的能力。

  2. 異步執行 (async):異步添加當前任務到指定隊列中,不會等待隊列的任務執行結束,直接開始執行下一個任務,可以在新的線程中執行任務,具備開啟線程的能力。

*注意:異步執行 (async)雖然具有開啟線程的能力,但是不一定會開啟新的線程,這跟任務所指定的隊列有關

2.隊列

隊列:存放任務的隊列,隊列是一種特殊的線性表,采用FIFO(先進先出)的原則,即新任務總是被插入到隊列末尾,而讀取任務總是從隊列的頭部開始讀取,每讀取一個任務,隊列中則釋放一個任務。

隊列有兩種方式,都滿足FIFO原則,主要區別是:執行順序不同,開啟線程數不同

  1. 串行隊列(Serial Dispatch Queue ):只開啟一個線程,一個任務執行完畢后,再執行下一個任務
  2. 并行隊列(Concurrent Dispatch Queue)?????????????? ??????????????????????????????????????????????????????????????:可以開啟多個線程,并且同時執行多個任務

3.使用步驟

  1. 創建一個隊列
  2. 將任務添加到隊列中,系統根據任務執行方式(同步、異步)進行執行

3.1 隊列的創建、獲取

使用dispatch_queue_create創建隊列
第一個參數:隊列的唯一標識符,用于DEBUG,可以為空,推薦使用應用程序ID這種逆序全局域名。
第二個參數:隊列類型,串行隊列DISPATCH_QUEUE_SERIAL,并行隊列DISPATCH_QUEUE_CONCURRENT

    //創建串行隊列
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.queque", DISPATCH_QUEUE_SERIAL);
    //創建并行隊列
    dispatch_queue_t queue2 = dispatch_queue_create("com.xiuxiu.queque", DISPATCH_QUEUE_CONCURRENT);
     

主隊列Main Dispatch Queue: GCD提供一種特殊串行隊列:

  1. 所有放在主隊列中的任務,都會放到主線程中執行
  2. 可使用dispatch_get_main_queue()獲取主隊列

主隊列獲取方法

    dispatch_queue_t queue = dispatch_get_main_queue()

全局并發隊列Global Dispatch Queue : GCD默認提供的全局并發隊列

并發隊列獲取方法:

 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_get_global_queue獲取全局隊列
第一個參數:隊列的優先級

#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

第二個參數:沒有使用,用0 即可

3.2 任務的執行方式


    dispatch_async(queue, ^{
         //異步執行任務代碼
    });
    
    dispatch_sync(queue, ^{
            //同步執行任務代碼
    });

第一個參數是隊列,那么隊列 + 任務 執行方式就有6種組合(加上主隊列)

同步執行 + 串行隊列
異步執行 + 串行隊列
同步執行 + 并行隊列
異步執行 + 并行隊列
同步執行 + 主隊列
異步執行 + 主隊列

3.2.1.同步執行 + 串行隊列

/*
 同步執行 + 串行隊列
 不會開啟新線程,在當前線程中執行任務,一個任務執行完畢后,再執行下一個任務
 */
- (void)syncSerial{
    NSLog(@" syncSerial  start");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.syncSerial", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" syncSerial  end");
}

前面的編號代表進程的編號,一個APP的就是一個進程,進程編號總是一致的;
后面的編號代表線程的編號, 21898代表線程的編號

輸出結果:

  1. 在當前線程中執行任務,沒有開啟新線程(同步執行不具備開啟線程能力),根據線程編號看出
  2. 所有任務都在syncSerial startsyncSerial end 之間執行(同步執行需要等待隊列中的任務執行結束)
  3. 任務按順序執行(串行隊列每次只有一個任務被執行,一個任務執行完畢后,再執行下一個任務)
2018-12-27 10:19:08.371664+0800 Thread[908:21898]  syncSerial  start
2018-12-27 10:19:08.371980+0800 Thread[908:21898]  任務一,i = 0
2018-12-27 10:19:09.372354+0800 Thread[908:21898]  任務一,i = 1
2018-12-27 10:19:10.372865+0800 Thread[908:21898]  任務一,i = 2
2018-12-27 10:19:11.373588+0800 Thread[908:21898]  任務二,i = 0
2018-12-27 10:19:12.374936+0800 Thread[908:21898]  任務二,i = 1
2018-12-27 10:19:13.375258+0800 Thread[908:21898]  任務二,i = 2
2018-12-27 10:19:14.376006+0800 Thread[908:21898]  syncSerial  end


3.2.2.異步執行 + 串行隊列

/*
 異步執行 + 串行隊列
 會開啟新線程,但是因為隊列是串行的,一個任務執行完畢后,再執行下一個任務
 */
- (void)asyncSerial{
    NSLog(@" asyncSerial  start");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.asyncSerial", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" asyncSerial  end");
}

輸出結果:

  1. 開啟一條線程(異步執行具備開啟線程的能力,因為是串行隊列,只開啟了一條線程)
  2. 所有任務都在asyncSerial startasyncSerial end 之后執行(異步執行不會等待,繼續執行任務)
  3. 任務按順序執行(串行隊列每次只有一個任務被執行,任務一個接著一個執行)
2018-12-27 10:43:17.947620+0800 Thread[1105:32316]  asyncSerial  start
2018-12-27 10:43:17.947916+0800 Thread[1105:32316]  asyncSerial  end
2018-12-27 10:43:17.948034+0800 Thread[1105:32360]  任務一,i = 0
2018-12-27 10:43:18.953286+0800 Thread[1105:32360]  任務一,i = 1
2018-12-27 10:43:19.956284+0800 Thread[1105:32360]  任務一,i = 2
2018-12-27 10:43:20.961804+0800 Thread[1105:32360]  任務二,i = 0
2018-12-27 10:43:21.965620+0800 Thread[1105:32360]  任務二,i = 1
2018-12-27 10:43:22.967181+0800 Thread[1105:32360]  任務二,i = 2

3.2.3. 同步執行 + 并行隊列

/*
 同步執行 + 并行隊列
 不會開啟新線程,在當前線程中執行任務,一個任務執行完畢后,再執行下一個任務
 */

- (void)syncConcurrent{
    NSLog(@" syncConcurrent  start");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.syncConcurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" syncConcurrent  end");
}

輸出結果:

  1. 在當前線程中執行任務,沒有開啟新線程(同步執行不具備開啟線程能力),根據線程編號看出,上面講過
  2. 所有任務都在syncConcurrent startsyncConcurrent end 之間執行(同步執行需要等待隊列中的任務執行結束)
  3. 任務按順序執行,雖然并行隊列可以開啟多個線程,并且同時執行多個任務,但是因為同步執行不具備開啟線程的能力,只有當前這一個線程,而且同步執行需要等待隊列中的任務執行結束,再執行下一個任務,所以任務只能一個接一個按照順序執行。
2018-12-27 10:54:22.992819+0800 Thread[1210:37289]  syncConcurrent  start
2018-12-27 10:54:22.993023+0800 Thread[1210:37289]  任務一,i = 0
2018-12-27 10:54:23.994423+0800 Thread[1210:37289]  任務一,i = 1
2018-12-27 10:54:24.995012+0800 Thread[1210:37289]  任務一,i = 2
2018-12-27 10:54:25.996057+0800 Thread[1210:37289]  任務二,i = 0
2018-12-27 10:54:26.997429+0800 Thread[1210:37289]  任務二,i = 1
2018-12-27 10:54:27.998885+0800 Thread[1210:37289]  任務二,i = 2
2018-12-27 10:54:29.000327+0800 Thread[1210:37289]  syncConcurrent  end

3.2.4.異步執行 + 并發隊列

/*
 異步執行 + 并行隊列
 開啟多個線程,任務交替執行
 */

- (void)asyncConcurrent{
    NSLog(@" asyncConcurrent  start");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.asyncConcurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" asyncConcurrent  end");
}

輸出結果:

  1. 開啟了2個新線程(異步執行具備開啟線程的能力)
  2. 所有任務都在asyncConcurrent startasyncConcurrent end 之后執行(異步執行不會等待,繼續執行任務)
  3. 任務交替執行(異步執行具備開啟線程的能力,并且并行隊列可以開啟多個線程,同時執行多個任務)
2018-12-27 11:18:23.174090+0800 Thread[1210:37289]  asyncConcurrent  start
2018-12-27 11:18:23.174253+0800 Thread[1210:37289]  asyncConcurrent  end
2018-12-27 11:18:23.174351+0800 Thread[1210:47038]  任務二,i = 0
2018-12-27 11:18:23.174401+0800 Thread[1210:37362]  任務一,i = 0
2018-12-27 11:18:24.177759+0800 Thread[1210:37362]  任務一,i = 1
2018-12-27 11:18:24.177759+0800 Thread[1210:47038]  任務二,i = 1
2018-12-27 11:18:25.178650+0800 Thread[1210:47038]  任務二,i = 2
2018-12-27 11:18:25.178650+0800 Thread[1210:37362]  任務一,i = 2

3.2.5.同步執行 + 主隊列

同步執行 + 主隊列 在不同線程中調用,結果不一樣。
在主線程中調用,出現死鎖
在其他線程中調用,不會開啟線程,一個任務執行完畢后,再執行下一個任務

3.2.5.1在主線程調用

/*
 同步執行 + 主隊列
 在主線程中調用,出現死鎖
 */

- (void)syncMain{
    NSLog(@" syncMain  start");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_sync(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" syncMain  end");
}

輸出結果:
在Xcode10 上運行崩潰,只輸出了syncMain start
為什么?
因為我們在主線程中執行 syncMain方法,相當于把 syncMain任務放到主線程的隊列中,而同步執行會等待當前隊列中的任務執行完畢后,才會接著執行。我們把任務一追加到主隊列中,任務一會等待主線程處理完syncMain方法,而syncMain方法又需要等待任務一執行完畢,才能繼續執行,雙方都在等待,所以線程死鎖,任務無法執行。

2018-12-27 11:26:17.011738+0800 Thread[1443:50531]  syncMain  start

3.2.5.2 在其他線程調用

/*
 同步執行 + 主隊列
 在其他線程中調用,不會開啟線程,一個任務執行完畢后,再執行下一個任務
 */

- (void)syncMain{
    NSLog(@"主線程");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@" syncMain  start");
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務一,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        dispatch_sync(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務二,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        NSLog(@" syncMain  end");
    });
}

輸出結果:

  1. 沒有開啟線程,放在主隊列的任務都在主線程中執行
  2. 所有任務都在syncMain startsyncMain end 之間執行(同步執行需要等待隊列中的任務執行結束)
  3. 任務按順序執行(串行隊列每次只有一個任務被執行,任務一個接著一個執行)
2018-12-27 14:22:03.964047+0800 Thread[2134:84333] 主線程
2018-12-27 14:22:03.964238+0800 Thread[2134:84385]  syncMain  start
2018-12-27 14:22:03.965009+0800 Thread[2134:84333]  任務一,i = 0
2018-12-27 14:22:04.966453+0800 Thread[2134:84333]  任務一,i = 1
2018-12-27 14:22:05.967903+0800 Thread[2134:84333]  任務一,i = 2
2018-12-27 14:22:06.968817+0800 Thread[2134:84333]  任務二,i = 0
2018-12-27 14:22:07.969877+0800 Thread[2134:84333]  任務二,i = 1
2018-12-27 14:22:08.970376+0800 Thread[2134:84333]  任務二,i = 2
2018-12-27 14:22:09.971810+0800 Thread[2134:84385]  syncMain  end

3.2.6.異步執行 + 主隊列

/*
 異步執行 + 主隊列
 不會開啟新線程,在主線程中執行,一個任務執行完畢后,再執行下一個任務
 */

- (void)asyncMain{
    NSLog(@" asyncMain  start");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@" asyncMain  end");
}

輸出結果:

  1. 沒有開啟線程,放在主隊列的任務都在主線程中執行
  2. 所有任務都在asyncMain startasyncMain end 之后執行(異步執行不會等待,繼續執行任務)
  3. 任務按順序執行(因為主隊列是串行隊列,每次只有一個任務被執行,任務一個接著一個執行)
2018-12-27 14:33:15.293261+0800 Thread[2134:84333]  asyncMain  start
2018-12-27 14:33:15.293406+0800 Thread[2134:84333]  asyncMain  end
2018-12-27 14:33:15.293646+0800 Thread[2134:84333]  任務一,i = 0
2018-12-27 14:33:16.293875+0800 Thread[2134:84333]  任務一,i = 1
2018-12-27 14:33:17.295251+0800 Thread[2134:84333]  任務一,i = 2
2018-12-27 14:33:18.296751+0800 Thread[2134:84333]  任務二,i = 0
2018-12-27 14:33:19.297602+0800 Thread[2134:84333]  任務二,i = 1
2018-12-27 14:33:20.298579+0800 Thread[2134:84333]  任務二,i = 2

4.線程間通信

在iOS開發工程中,我們一般在主線程中進行UI刷新,如:點擊、拖拽、滾動事件,耗時操作放在其他線程中,而當耗時操作結束后,回到主線程,就需要用到線程間的通信。

在全局隊列中執行任務,任務完成后,切回主線程

- (void)gcdCommunication{
    NSLog(@"我在主線程");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主線程");
        });
    });
}

輸出結果:

2018-12-27 15:05:55.188427+0800 Thread[2519:102865] 我在主線程
2018-12-27 15:05:55.188635+0800 Thread[2519:102899]  任務一,i = 0
2018-12-27 15:05:56.189689+0800 Thread[2519:102899]  任務一,i = 1
2018-12-27 15:05:57.191253+0800 Thread[2519:102899]  任務一,i = 2
2018-12-27 15:05:58.195350+0800 Thread[2519:102865] 回到主線程

5.dispatch_once

創建單例或者整個程序只運行一次的代碼,可以使用dispatch_oncedispatch_once函數保證這個程序運行過程中只被執行一次,即時在多線程情況下也是安全的。

/*
   驗證dispatch_once
 */
- (void)testGcdOnce{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            [self gcdOnce];
        }
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            [self gcdOnce];
        }
    });
}

- (void)gcdOnce{
    NSLog(@"%s",__func__);
    static TicketManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[TicketManager alloc]init];
        NSLog(@"創建對象");
    });
}

輸出結果:dispatch_once里面只被執行了一次

2018-12-27 15:19:31.228794+0800 Thread[2702:110251] -[ViewController gcdOnce]
2018-12-27 15:19:31.228794+0800 Thread[2702:110250] -[ViewController gcdOnce]
2018-12-27 15:19:31.229290+0800 Thread[2702:110251] 創建對象
2018-12-27 15:19:31.229516+0800 Thread[2702:110250] -[ViewController gcdOnce]
2018-12-27 15:19:31.229514+0800 Thread[2702:110251] -[ViewController gcdOnce]
2018-12-27 15:19:31.229630+0800 Thread[2702:110250] -[ViewController gcdOnce]
2018-12-27 15:19:31.229678+0800 Thread[2702:110251] -[ViewController gcdOnce]

6.dispatch_after

dispatch_after并不是在指定時間之后才執行處理,而是在指定時間之后將任務追加到隊列中,這個指定時間并不是絕對準確的,想要大致完成延時任務可以使用dispatch_after函數實現

- (void)gcdAfter{
    NSLog(@"%s",__func__);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%s",__func__);
    });;
}

輸出結果:大致為1秒

2018-12-27 15:28:23.416958+0800 Thread[2781:114534] -[ViewController gcdAfter]
2018-12-27 15:28:24.513064+0800 Thread[2781:114534] -[ViewController gcdAfter]_block_invoke

7. dispatch_apply(快速迭代)

dispatch_apply快速迭代方法,按照指定次數將指定的任務添加到隊列中,并等待隊列中的任務全部執行結束。

如果在串行隊列中使用dispatch_apply函數,就和for循環遍歷一樣,按照順序執行,體現不出快速迭代的意義。

如果在并行隊列中使用dispatch_apply函數,dispatch_apply可以在多個線程中同時遍歷多個數字。

7.1 在串行隊列使用dispatch_apply

將在主隊列dispatch_get_main_queue的遍歷任務放在并行隊列dispatch_get_global_queue中,為了避免上面講的死鎖問題,關注apply beginapply end之間的代碼即可

/*
 dispatch_apply:快速迭代
 */
- (void)gcdApply{
    NSLog(@"主線程");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"apply begin");
        dispatch_apply(6, dispatch_get_main_queue(), ^(size_t index) {
            NSLog(@"index = %zu",index);
        });
        NSLog(@"apply end");
    });
   
}

輸出結果:

  1. 沒有開啟線程(主隊列的任務只在主線程中執行)
  2. apply end在最后輸出(dispatch_apply函數會等待隊列全部任務執行結束)
2018-12-27 15:58:28.788666+0800 Thread[3090:128936] 主線程
2018-12-27 15:58:28.788880+0800 Thread[3090:128988] apply begin
2018-12-27 15:58:28.819630+0800 Thread[3090:128936] index = 0
2018-12-27 15:58:28.819791+0800 Thread[3090:128936] index = 1
2018-12-27 15:58:28.819897+0800 Thread[3090:128936] index = 2
2018-12-27 15:58:28.820107+0800 Thread[3090:128936] index = 3
2018-12-27 15:58:28.820396+0800 Thread[3090:128936] index = 4
2018-12-27 15:58:28.820503+0800 Thread[3090:128936] index = 5
2018-12-27 15:58:28.820752+0800 Thread[3090:128988] apply end


7.2在并行隊列使用dispatch_apply

/*
 dispatch_apply:快速迭代
 */
- (void)gcdApply{
    NSLog(@"apply begin");
    dispatch_apply(6, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"index = %zu",index);
    });
    NSLog(@"apply end");
}

輸出結果:

  1. 開啟線程(看線程編號得出)
  2. apply end在最后輸出(dispatch_apply函數會等待隊列全部任務執行結束)
2018-12-27 15:39:06.219402+0800 Thread[2918:120358] apply begin
2018-12-27 15:39:06.219636+0800 Thread[2918:120391] index = 1
2018-12-27 15:39:06.219619+0800 Thread[2918:120358] index = 0
2018-12-27 15:39:06.219746+0800 Thread[2918:120391] index = 3
2018-12-27 15:39:06.219747+0800 Thread[2918:120392] index = 4
2018-12-27 15:39:06.219737+0800 Thread[2918:120358] index = 2
2018-12-27 15:39:06.219832+0800 Thread[2918:120391] index = 5
2018-12-27 15:39:06.219933+0800 Thread[2918:120358] apply end

需求1:我們需要異步執行兩個操作(一個操作可以是一個任務,也可以是多個任務,這里是兩個任務),而且第一組操作結束后,才能開始第二組操作,如何實現呢?

這里的意思其實保持線程同步,將異步任務轉化為同步任務的意思。

方法1:使用柵欄函數
方法2:使用dispatch_group
方式3 :使用dispatch_semaphore

這三個方法都會在下面一一講解的。

總結:就當前這個需求,使用柵欄函數會比較簡單,所有方法講完具體實現就可以看出來了不用創建group或者semaphore,直接放個柵欄在中間,分隔兩個操作。

8.dispatch_barrier(柵欄函數)

柵欄函數存在的意義:先執行柵欄函數之前的任務,再執行柵欄函數中的任務,最后執行柵欄函數之后的任務,增加柵欄函數不影響原有隊列的任務執行方式,也就是柵欄函數之前隊列的任務是什么執行方式,柵欄函數之后隊列的任務還是什么執行方式。

柵欄函數分為:dispatch_barrier_asyncdispatch_barrier_sync
區別就是 :添加柵欄函數后面任務到隊列的時間不一樣。

dispatch_barrier_sync需要等待柵欄函數中的任務執行結束后,才會添加柵欄函數后的任務到隊列中。

dispatch_barrier_async不需要等待柵欄函數中的任務執行結束,就已經將柵欄函數后的任務到隊列中。

8.1 dispatch_barrier_sync

實現代碼

- (void)gcdBarrier{
    NSLog(@"主線程");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.gcd", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    //1.dispatch_barrier_sync
    dispatch_barrier_sync(queue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            NSLog(@" barrier1,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        for (NSInteger i = 0; i < 3; i ++) {
            NSLog(@" barrier2,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
 
    NSLog(@"---------barrier代碼后面----------------");
    
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務三,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務四,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
}

輸出結果:

  1. 執行任務一、二,執行柵欄函數中的barrier1barrier2最后執行任務三、四;
  2. 任務一、二交替執行(異步執行),任務三、四交替執行(異步執行),印證柵欄函數不影響隊列任務的執行方式;
  3. 橫線在柵欄函數之后輸出(dispatch_barrier_sync:需要等待柵欄函數中的任務執行結束后,才會添加柵欄函數后的任務到隊列中)。
2018-12-28 17:07:54.227141+0800 Thread[11766:169507] 主線程
2018-12-28 17:07:54.227455+0800 Thread[11766:169563]  任務二,i = 0
2018-12-28 17:07:54.227482+0800 Thread[11766:169565]  任務一,i = 0
2018-12-28 17:07:55.231426+0800 Thread[11766:169563]  任務二,i = 1
2018-12-28 17:07:55.231426+0800 Thread[11766:169565]  任務一,i = 1
2018-12-28 17:07:56.232952+0800 Thread[11766:169563]  任務二,i = 2
2018-12-28 17:07:56.232985+0800 Thread[11766:169565]  任務一,i = 2
2018-12-28 17:07:57.236980+0800 Thread[11766:169507]  barrier1,i = 0
2018-12-28 17:07:58.238423+0800 Thread[11766:169507]  barrier1,i = 1
2018-12-28 17:07:59.239776+0800 Thread[11766:169507]  barrier1,i = 2
2018-12-28 17:08:00.240315+0800 Thread[11766:169507]  barrier2,i = 0
2018-12-28 17:08:01.240863+0800 Thread[11766:169507]  barrier2,i = 1
2018-12-28 17:08:02.242310+0800 Thread[11766:169507]  barrier2,i = 2
2018-12-28 17:08:03.242826+0800 Thread[11766:169507] ---------barrier代碼后面----------------
2018-12-28 17:08:03.243200+0800 Thread[11766:169564]  任務三,i = 0
2018-12-28 17:08:03.243315+0800 Thread[11766:169663]  任務四,i = 0
2018-12-28 17:08:04.247765+0800 Thread[11766:169663]  任務四,i = 1
2018-12-28 17:08:04.247765+0800 Thread[11766:169564]  任務三,i = 1
2018-12-28 17:08:05.252405+0800 Thread[11766:169663]  任務四,i = 2
2018-12-28 17:08:05.252405+0800 Thread[11766:169564]  任務三,i = 2



8.2dispatch_barrier_async

實現代碼

 - (void)gcdBarrier{
    NSLog(@"主線程");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.gcd", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    //2.dispatch_barrier_async
    dispatch_barrier_async(queue, ^{
        for (NSInteger i = 0; i < 3; i ++) {
            NSLog(@" barrier1,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        for (NSInteger i = 0; i < 3; i ++) {
            NSLog(@" barrier2,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    NSLog(@"---------barrier代碼后面----------------");
    
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務三,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務四,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
}


輸出結果:

  1. 執行任務一、二,執行柵欄函數中的barrier1barrier2最后執行任務三、四;
  2. 任務一、二交替執行(異步執行),任務三、四交替執行(異步執行),印證柵欄函數不影響隊列任務的執行方式;
  3. 橫線在柵欄函數之前輸出(dispatch_barrier_async不需要等待柵欄函數中的任務執行結束,就已經將柵欄函數后的任務到隊列中)。
2018-12-28 17:10:58.908322+0800 Thread[11798:171154] 主線程
2018-12-28 17:10:58.908586+0800 Thread[11798:171209]  任務一,i = 0
2018-12-28 17:10:58.908586+0800 Thread[11798:171154] ---------barrier代碼后面----------------
2018-12-28 17:10:58.908616+0800 Thread[11798:171207]  任務二,i = 0
2018-12-28 17:10:59.912811+0800 Thread[11798:171207]  任務二,i = 1
2018-12-28 17:10:59.912811+0800 Thread[11798:171209]  任務一,i = 1
2018-12-28 17:11:00.917006+0800 Thread[11798:171207]  任務二,i = 2
2018-12-28 17:11:00.917047+0800 Thread[11798:171209]  任務一,i = 2
2018-12-28 17:11:01.919238+0800 Thread[11798:171209]  barrier1,i = 0
2018-12-28 17:11:02.921574+0800 Thread[11798:171209]  barrier1,i = 1
2018-12-28 17:11:03.924472+0800 Thread[11798:171209]  barrier1,i = 2
2018-12-28 17:11:04.929280+0800 Thread[11798:171209]  barrier2,i = 0
2018-12-28 17:11:05.930060+0800 Thread[11798:171209]  barrier2,i = 1
2018-12-28 17:11:06.931831+0800 Thread[11798:171209]  barrier2,i = 2
2018-12-28 17:11:07.934022+0800 Thread[11798:171209]  任務三,i = 0
2018-12-28 17:11:07.934022+0800 Thread[11798:171207]  任務四,i = 0
2018-12-28 17:11:08.939572+0800 Thread[11798:171207]  任務四,i = 1
2018-12-28 17:11:08.939573+0800 Thread[11798:171209]  任務三,i = 1
2018-12-28 17:11:09.942792+0800 Thread[11798:171209]  任務三,i = 2
2018-12-28 17:11:09.942829+0800 Thread[11798:171207]  任務四,i = 2


9.dispatch_group(隊列組)

任務的執行是先創建一個任務,放入隊列進行執行。而隊列組就是用來存放隊列的一個組。

將隊列放入隊列組中可以使用 dispatch_group_async 或者dispatch_group_enterdispatch_group_leave的組合實現。

隊列組的任務結束完成后,調用dispatch_group_notify 可以回到指定線程執行任務,調用dispatch_group_wait可以回到當前線程執行任務(阻塞當前線程)。

就上面的需求1,用dispatch_group方式實現

1. 我們用dispatch_group_async + dispatch_group_notify來進行實現

- (void)requirementGroupAsync{
    NSLog(@"主線程");
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務一,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        
    });
    dispatch_group_async(group, queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"------dispatch_group_notify------");
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務三,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務四,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        
    });
}

輸出結果:先執行任務一、二,再執行任務三、四;

2018-12-28 17:59:23.695495+0800 Thread[12227:189820] 主線程
2018-12-28 17:59:23.695775+0800 Thread[12227:189867]  任務一,i = 0
2018-12-28 17:59:23.695778+0800 Thread[12227:189868]  任務二,i = 0
2018-12-28 17:59:24.697513+0800 Thread[12227:189867]  任務一,i = 1
2018-12-28 17:59:24.697514+0800 Thread[12227:189868]  任務二,i = 1
2018-12-28 17:59:25.701243+0800 Thread[12227:189868]  任務二,i = 2
2018-12-28 17:59:25.701243+0800 Thread[12227:189867]  任務一,i = 2
2018-12-28 17:59:26.705407+0800 Thread[12227:189868] ------dispatch_group_notify------
2018-12-28 17:59:26.705799+0800 Thread[12227:189868]  任務三,i = 0
2018-12-28 17:59:26.705803+0800 Thread[12227:189866]  任務四,i = 0
2018-12-28 17:59:27.709876+0800 Thread[12227:189868]  任務三,i = 1
2018-12-28 17:59:27.709873+0800 Thread[12227:189866]  任務四,i = 1
2018-12-28 17:59:28.713711+0800 Thread[12227:189866]  任務四,i = 2
2018-12-28 17:59:28.713711+0800 Thread[12227:189868]  任務三,i = 2

2. 我們用dispatch_group_async + dispatch_group_wait來進行實現

- (void)requirementGroupAsync{
    NSLog(@"主線程");
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務一,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        
    });
    dispatch_group_async(group, queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    //2.dispatch_group_wait
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"-----dispatch_group_wait----");
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務三,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務四,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    
}

輸出結果:

  1. 先執行任務一、二,再執行任務三、四;
  2. dispatch_group_wait在主線程輸出,就如之前所說 dispatch_group_wait會阻塞當前線程。
2018-12-28 18:07:54.379565+0800 Thread[12300:193286] 主線程
2018-12-28 18:07:54.379782+0800 Thread[12300:193334]  任務二,i = 0
2018-12-28 18:07:54.379783+0800 Thread[12300:193332]  任務一,i = 0
2018-12-28 18:07:55.384688+0800 Thread[12300:193334]  任務二,i = 1
2018-12-28 18:07:55.384720+0800 Thread[12300:193332]  任務一,i = 1
2018-12-28 18:07:56.385807+0800 Thread[12300:193332]  任務一,i = 2
2018-12-28 18:07:56.385808+0800 Thread[12300:193334]  任務二,i = 2
2018-12-28 18:07:57.386847+0800 Thread[12300:193286] -----dispatch_group_wait----
2018-12-28 18:07:57.387161+0800 Thread[12300:193334]  任務三,i = 0
2018-12-28 18:07:57.387174+0800 Thread[12300:193332]  任務四,i = 0
2018-12-28 18:07:58.387806+0800 Thread[12300:193332]  任務四,i = 1
2018-12-28 18:07:58.387808+0800 Thread[12300:193334]  任務三,i = 1
2018-12-28 18:07:59.390173+0800 Thread[12300:193332]  任務四,i = 2
2018-12-28 18:07:59.390209+0800 Thread[12300:193334]  任務三,i = 2

3. 我們用dispatch_group_enter + dispatch_group_leave + dispatch_group_notify來進行實現

- (void)requirementGroupEnter{
    NSLog(@"主線程");
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_group_leave(group);
    });

    //1.dispatch_group_notify
    dispatch_group_notify(group, queue, ^{
        NSLog(@"------dispatch_group_notify------");
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務三,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務四,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        
    });
}

輸出結果:先執行任務一、二,再執行任務三、四;

2018-12-28 18:19:52.294127+0800 Thread[12422:198467] 主線程
2018-12-28 18:19:52.294429+0800 Thread[12422:198501]  任務一,i = 0
2018-12-28 18:19:52.294438+0800 Thread[12422:198504]  任務二,i = 0
2018-12-28 18:19:53.297632+0800 Thread[12422:198504]  任務二,i = 1
2018-12-28 18:19:53.297642+0800 Thread[12422:198501]  任務一,i = 1
2018-12-28 18:19:54.302891+0800 Thread[12422:198501]  任務一,i = 2
2018-12-28 18:19:54.302891+0800 Thread[12422:198504]  任務二,i = 2
2018-12-28 18:19:55.307933+0800 Thread[12422:198501] ------dispatch_group_notify------
2018-12-28 18:19:55.308242+0800 Thread[12422:198501]  任務三,i = 0
2018-12-28 18:19:55.308258+0800 Thread[12422:198502]  任務四,i = 0
2018-12-28 18:19:56.312331+0800 Thread[12422:198501]  任務三,i = 1
2018-12-28 18:19:56.312331+0800 Thread[12422:198502]  任務四,i = 1
2018-12-28 18:19:57.313584+0800 Thread[12422:198502]  任務四,i = 2
2018-12-28 18:19:57.313584+0800 Thread[12422:198501]  任務三,i = 2

4. 我們用dispatch_group_enter + dispatch_group_leave + dispatch_group_wait來進行實現

- (void)requirementGroupEnter{
    NSLog(@"主線程");
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務二,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        dispatch_group_leave(group);
    });
//    2.dispatch_group_wait
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"-----dispatch_group_wait----");
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務三,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
        dispatch_async(queue, ^{
            for (NSInteger  i = 0; i < 3; i ++) {
                NSLog(@" 任務四,i = %ld",(long)i);
                [NSThread sleepForTimeInterval:1.0];
            }
        });
    
}

  1. 先執行任務一、二,再執行任務三、四;
  2. dispatch_group_wait在主線程輸出,就如之前所說 dispatch_group_wait會阻塞當前線程
2018-12-28 18:21:29.197243+0800 Thread[12446:199547] 主線程
2018-12-28 18:21:29.197534+0800 Thread[12446:199590]  任務一,i = 0
2018-12-28 18:21:29.197598+0800 Thread[12446:199591]  任務二,i = 0
2018-12-28 18:21:30.200867+0800 Thread[12446:199591]  任務二,i = 1
2018-12-28 18:21:30.200870+0800 Thread[12446:199590]  任務一,i = 1
2018-12-28 18:21:31.204217+0800 Thread[12446:199590]  任務一,i = 2
2018-12-28 18:21:31.204215+0800 Thread[12446:199591]  任務二,i = 2
2018-12-28 18:21:32.205551+0800 Thread[12446:199547] -----dispatch_group_wait----
2018-12-28 18:21:32.205926+0800 Thread[12446:199590]  任務三,i = 0
2018-12-28 18:21:32.205930+0800 Thread[12446:199591]  任務四,i = 0
2018-12-28 18:21:33.210243+0800 Thread[12446:199590]  任務三,i = 1
2018-12-28 18:21:33.210413+0800 Thread[12446:199591]  任務四,i = 1
2018-12-28 18:21:34.215728+0800 Thread[12446:199590]  任務三,i = 2
2018-12-28 18:21:34.215728+0800 Thread[12446:199591]  任務四,i = 2

dispatch_group_asyncdispatch_group_enter + dispatch_group_leave有什么區別呢?

需求2:異步執行兩個網絡請求,兩個網絡請求執行結束后,進行一定的操作。

這里sendRequest用來代表網絡請求

- (void)sendRequest:(void (^)(void))block{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 1");
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });
    });
}

- (void)sendRequest2:(void (^)(void))block{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"start task 2");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"end task 2");
        dispatch_async(dispatch_get_main_queue(), ^{
            if (block) {
                block();
            }
        });
    });
}

1. 用dispatch_group_async實現

- (void)gcdGroupAsync{
    NSLog(@"主線程");
//    dispatch_group_async 里面,應該放同步代碼,而不是異步代碼
        dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_async(group, queue, ^{
    
            [self sendRequest:^{
                NSLog(@"sendRequest done");
            }];
        });
    
        dispatch_group_async(group, queue, ^{
            [self sendRequest2:^{
                NSLog(@"sendRequest2 done");
            }];
        });
        dispatch_group_notify(group, queue, ^{
            NSLog(@"all task over");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"回到主線程刷新UI");
            });
        });
}

輸出結果:顯示不符合需求,task1和task2還沒有結束,就輸出all task over 咯,為啥子呢?
原因:因為dispatch_group_async里面放入的是異步的任務,dispatch_group_async執行了sendRequest這行代碼后,就認為sendRequest已經執行完畢了(其實還沒有回調回來),group不再持有這個任務,就會執行下面的dispatch_group_async,而sendRequest2同理,group沒有任務時,就會執行dispatch_group_notify 里面的任務,所以造成這樣子的輸出結果。

由此可見:dispatch_group_async里面適合放入同步代碼,而不是異步代碼。

2018-12-29 17:13:42.710421+0800 Thread[17395:417245] 主線程
2018-12-29 17:13:42.710700+0800 Thread[17395:417334] start task 2
2018-12-29 17:13:42.710726+0800 Thread[17395:417333] all task over
2018-12-29 17:13:42.710708+0800 Thread[17395:417331] start task 1
2018-12-29 17:13:42.733662+0800 Thread[17395:417245] 回到主線程刷新UI
2018-12-29 17:13:45.714431+0800 Thread[17395:417331] end task 1
2018-12-29 17:13:45.714511+0800 Thread[17395:417334] end task 2
2018-12-29 17:13:45.714752+0800 Thread[17395:417245] sendRequest done
2018-12-29 17:13:45.714854+0800 Thread[17395:417245] sendRequest2 done

2.用dispatch_group_enter + dispatch_group_leave 實現

- (void)gcdGroupEnter{
    NSLog(@"主線程");
  
    dispatch_queue_t queue = dispatch_queue_create("com.gcd.group", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    [self sendRequest:^{
        NSLog(@"sendRequest done");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_enter(group);
    [self sendRequest2:^{
        NSLog(@"sendRequest2 done");
        dispatch_group_leave(group);
    }];
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"all task over");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"回到主線程刷新UI");
        });
    });
}

輸出結果:當task1 和task2執行結束后,才輸出all task over,符合我們的需求。
當我們調用sendRequest時,先調用dispatch_group_enter時,任務回調后再調用dispatch_group_leave,整個異步操作中,任務是被group持有的,只有回調結束后才離開group,所以不會出現上面的問題。

注意:dispatch_group_enterdispatch_group_leave成對存在

2018-12-29 17:32:12.739361+0800 Thread[17557:425760] 主線程
2018-12-29 17:32:12.739608+0800 Thread[17557:425809] start task 1
2018-12-29 17:32:12.739608+0800 Thread[17557:425810] start task 2
2018-12-29 17:32:15.742987+0800 Thread[17557:425809] end task 1
2018-12-29 17:32:15.742990+0800 Thread[17557:425810] end task 2
2018-12-29 17:32:15.743325+0800 Thread[17557:425760] sendRequest2 done
2018-12-29 17:32:15.743528+0800 Thread[17557:425760] sendRequest done
2018-12-29 17:32:15.743742+0800 Thread[17557:425810] all task over
2018-12-29 17:32:15.744000+0800 Thread[17557:425760] 回到主線程刷新UI


10.dispatch_semaphore(信號量)

dispatch_semaphore 使用計數來完成這個問題,計數為0 ,不可以通過,計數大于等于1,可以通過
其中有三個函數分別為:

  1. dispatch_semaphore_create :創建semaphore并初始化信號量,初始化的值大于等于0;
  2. dispatch_semaphore_signal:信號量+1;
  3. dispatch_semaphore_wait : 判斷當前信號量的值,如果當前信號量大于0,信號量-1,往下執行,如果當前信號量等于0,就會阻塞在當前線程,一直等待。

用處:

1、保持線程同步,將異步任務轉化為同步任務
2、保證線程安全,為線程加鎖

線程安全:在多個線程中同時訪問并操作同一對象時,運行結果與預期的值相同就是線程安全。
線程安全問題都是由全局變量及靜態變量引起的,若每個線程中對全局變量靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。

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

10.1.保持線程同步,將異步任務轉化為同步任務

這個也可以用來實現上面的需求1

- (void)gcdSemaphore{
    NSLog(@"主線程");
    dispatch_queue_t queue = dispatch_queue_create("com.xiuxiu.gcd", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務一,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
        long x = dispatch_semaphore_signal(semaphore);
        NSLog(@"signal后的信號量 = %ld",x);
    });
    long x = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"wait后的信號量 = %ld",x);
    NSLog(@"---dispatch_semaphore_wait-----");
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務三,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
    dispatch_async(queue, ^{
        for (NSInteger  i = 0; i < 3; i ++) {
            NSLog(@" 任務四,i = %ld",(long)i);
            [NSThread sleepForTimeInterval:1.0];
        }
    });
}

輸出結果:

  1. 先輸出任務一,再輸出任務三、四
  2. dispatch_semaphore_wait在主線程輸出(信號量為0 ,阻塞在當前線程)

我們在主線程創建了一個信號量賦值為0,并開辟了一個并行隊列異步執行任務一,因為是一個異步操作,此時不會等待任務一執行結束, 直接執行到dispatch_semaphore_wait,此時判斷出信號量的值0,不可通行,阻塞當前線程,當運行到dispatch_semaphore_signal時,信號量加1后等于1大于0,可通行,執行dispatch_semaphore_wait后面的任務三、四,通行后信號量減1等于0。

2018-12-29 15:22:24.960944+0800 Thread[16315:368237] 主線程
2018-12-29 15:22:24.961180+0800 Thread[16315:368286]  任務一,i = 0
2018-12-29 15:22:25.965568+0800 Thread[16315:368286]  任務一,i = 1
2018-12-29 15:22:26.970160+0800 Thread[16315:368286]  任務一,i = 2
2018-12-29 15:22:27.975627+0800 Thread[16315:368237] wait后的信號量 = 0
2018-12-29 15:22:27.975627+0800 Thread[16315:368286] signal后的信號量 = 1
2018-12-29 15:22:27.975956+0800 Thread[16315:368237] ---dispatch_semaphore_wait-----
2018-12-29 15:22:27.976232+0800 Thread[16315:368287]  任務四,i = 0
2018-12-29 15:22:27.976285+0800 Thread[16315:368286]  任務三,i = 0
2018-12-29 15:22:28.978431+0800 Thread[16315:368286]  任務三,i = 1
2018-12-29 15:22:28.978445+0800 Thread[16315:368287]  任務四,i = 1
2018-12-29 15:22:29.980405+0800 Thread[16315:368286]  任務三,i = 2
2018-12-29 15:22:29.980405+0800 Thread[16315:368287]  任務四,i = 2

10.2.保證線程安全,為線程加鎖

在上面講NSThread的時候,講過synchronizedNSCondition加鎖方式,這里使用dispatch_semaphore進行加密,運行結果和上面一致。

- (void)sale{
    while (1) {
        //1、synchronized
//        @synchronized (self) {
//            if (self.tickets > 0 ) {
//                [NSThread sleepForTimeInterval:0.1];
//                self.tickets --;
//                self.saleCount = Total - self.tickets;
//                NSLog(@"%@ , 賣出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//            }else{
//                break;//一定要break,不然就會死循環
//            }
//        }
//        2、NSCondition
//        [self.condition lock];
//        if (self.tickets > 0 ) {
//            [NSThread sleepForTimeInterval:0.1];
//            self.tickets --;
//            self.saleCount = Total - self.tickets;
//            NSLog(@"%@ , 賣出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
//        }else{
//            break;
//        }
//        [self.condition unlock];
//
        //3、dispatch_semaphore方式
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        if (self.tickets > 0 ) {
            [NSThread sleepForTimeInterval:0.1];
            self.tickets --;
            self.saleCount = Total - self.tickets;
            NSLog(@"%@ , 賣出 = %d,剩余= %d",[NSThread currentThread].name,self.saleCount,self.tickets);
        }else{
            dispatch_semaphore_signal(self.semaphore);
            break;
        }
        dispatch_semaphore_signal(self.semaphore);
    }
}

信號量還需要多看點資料,這里就先這樣子吧~~

上面就保持線程同步,將異步任務轉化為同步任務,保證線程安全,給線程加鎖就講了好多種方式,選擇的時候,針對需求而言來選擇一個較好的方式就OK啦~

參考博客:

iOS多線程慕課網視頻
深入理解iOS開發中的鎖

文章鏈接:
iOS 多線程- pThread和NSThread
iOS 多線程-NSOperation + NSOperationQueue

喜歡就點個贊吧????
有錯之處,還請指出,感謝????

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容