iOS 開發之 GCD解析(block 如何被添加進 queue 中,以及 block 執行)

分類:編程 時間:2017-02-15

GCD 是系統為我們提供的一套 c 語言的 API,可以用來進行多線程編程,下面一次來講解一下 GCD 的相關 API
首先先來理解 幾個概念


串行和並發;同步與異步


1. 串行和並發

串行:一次只能有一個任務執行
並發:在某一時間間隔內,有兩個或者兩個以上的任務一起執行(本質還是串行的,只不過按照時間片輪轉的方式交替執行)。

2. 同步和異步

  1. 同步:同步就是任務要等到前面的任務執行完成之後他才可以執行,不會新開辟線程,所以對於一些耗時操作要放到子線程,不然會阻塞主線程
  2. 異步:不用等待前面的執行完,會新開辟一條線程去執行該任務。

GCD 的 相關概念


GCD 是系統為我們提供的一套 c 語言的 API,不是 Objective-c的對象,是對 NSThread 的封裝。

1. Serial Dispatch Queue 和Concunrrent Dispatch Queue

*. Serial dispach Queue:串行隊列
  1. 一次只能有一個任務運行
  2. 可以生成很多個,創建了多少個串行隊列就生成了多少個線程,但是對於系統來說大量的創建串行隊列,會浪費內存,引起大量的上下文切換,影響程序的響應性能
*. Concunrrent Dispatch Queue:並發隊列
  1. 某一時間間隔裏可以有多個任務同時運行
  2. 並發隊列中並發任務的數量是由 XNU 內核的狀態決定的
  3. 相對於串行隊列,不管生成了多少個Concunrrent Dispatch Queue:,系統指揮管理有效的並發隊列
  4. 當創建了多個並發隊列的時候,各個隊列之間是並發的。

2. Main Dispatch Queue 和Global Dispatch Queue

這是系統為我們默認提供的

*. Main Dispatch Queue:主隊列
  1. 當程序開始運行時,系統會為我們創建一個主線程,諸葛主隊列就在主線程裏面,因為主線程只有一個,所以主隊列也就只有一個,因此主隊列是一個串行隊列
  2. 被加入到主隊列的任務,必須在主 runloop 中執行(runloop 與線程的關系請點這裏runloop 與線程的關系)
  3. 主隊列是由系統默認創建的,可以通過dispatch_get_main_queue()的方式來獲取
  4. 對於界面的刷新是要放到主隊列的.

*. Global Dispatch Queue:全局隊列

  1. 一個全局隊列,所有的應用程序都可以用,是並發的
  2. Global Dispatch Queue:有四個優先級
    *. 高優先級
    *. 低優先級
    *. 默認優先級
    *. 後臺優先級
    由 XNU 內核管理的用於Global Dispatch Queue的的線程會將Global Dispatch Queue的的優先級作為線程的優先級。

GCD的 API


1. dispatch_queue_create(”queue名”,queue 屬性)

  1. 第一個參數是隊列名,第二個參數是隊列的屬性,返回值是 dispatch_queue_t 類型的
  2. 當第二個參數是 NULL,創建的是串行隊列
 dispatch_queue_t serialQueue =  dispatch_queue_create("serial dispatch queue",  NULL);
    NSLog(@"%@",serialQueue);
    //打印結果:<OS_dispatch_queue: serial dispatch queue[0x608000168880]>

當第二個參數是DISPATCH_QUEUE_CONCURRENT,創建的是並發隊列

 dispatch_queue_t concurrentQueue =  dispatch_queue_create("concurrent dispatch queue",  DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"%@",concurrentQueue);
   //打印結果:<OS_dispatch_queue: concurrent dispatch queue[0x600000165580]>

2. dispatch_async:創建異步任務(指定的 queue,要執行的 block 塊)

  1. 第一個參數是隊列(串行還是並發),第二個是我們要執行的任務塊
  2. 會開啟一個新的線程,來執行 block 中的內容,要是刷新界面需要切換到主隊列
  3. 代碼如下
        NSLog(@"開啟一個異步任務%@",[NSThread currentThread]);
    });

2. dispatch_sync(指定的 queue,要執行的 block 塊)

  1. 創建同步任務,不會開啟新的線程,所以會阻塞主線程
  2. 會造成死鎖
  3. 代碼如下
dispatch_sync(Queue, ^{
        NSLog(@"開啟一個同步任務");
    });

3. dispatch_set_target_queue:(Queue1,Queue2)改變隊列的優先級

  1. 修改隊列的優先級
    dispatch_queue_t test1 = dispatch_queue_create("test1",NULL);    dispatch_queue_t test2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);   
     dispatch_set_target_queue(test1, test2);
//第一個是要是修改優先級的 queue,第二個是參照物,將 test1的優先級設置為和 test2一樣

應用:不管是並行隊列還是串行隊列,當任務以異步的方式被添加到隊列中,隊列之間是並發的,要想讓隊列之間同步,可以將要同步的隊列的目標隊列設置為同一個,如下例子

    dispatch_queue_t test1 =  dispatch_queue_create("test1 dispatch queue", NULL );
    dispatch_queue_t test2 =  dispatch_queue_create("test2 dispatch queue1", NULL);
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我在test1 %d",i);
        }
    });
    dispatch_async(test2, ^{
        for (int i = 0; i<5; i++) {
        NSLog(@"我在test2 %d",i);
        }
    });   
//運行結果如下
2017-02-14 10:48:56.371 AFNetworkingTestDemo[23510:1106181] 我在test1 0
2017-02-14 10:48:56.371 AFNetWorkingTestDemo[23510:1106181] 我在test1 1
2017-02-14 10:48:56.371 AFNetWorkingTestDemo[23510:1106174] 我在test2 0
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106181] 我在test1 2
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106181] 我在test1 3
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106181] 我在test1 4
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106174] 我在test2 1
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106174] 我在test2 2
2017-02-14 10:48:56.372 AFNetWorkingTestDemo[23510:1106174] 我在test2 3
2017-02-14 10:48:56.373 AFNetWorkingTestDemo[23510:1106174] 我在test2 4

可以看出兩個隊列交替打印,所以說隊列之間是並發的
修改代碼如下

    dispatch_queue_t test1 =  dispatch_queue_create("test1 dispatch queue", NULL );
    dispatch_queue_t test2 =  dispatch_queue_create("test2 dispatch queue1", NULL);
    dispatch_set_target_queue(test2, test1);//加了 該行代碼
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我在test1 %d",i);
        }
    });
    dispatch_async(test2, ^{
        for (int i = 0; i<5; i++) {
        NSLog(@"我在test2 %d",i);
        }
    });
 //打印結果如下
2017-02-14 10:48:03.311 AFNetWorkingTestDemo[23484:1105222] 我在test1 0
2017-02-14 10:48:03.312 AFNetWorkingTestDemo[23484:1105222] 我在test1 1
2017-02-14 10:48:03.312 AFNetWorkingTestDemo[23484:1105222] 我在test1 2
2017-02-14 10:48:03.313 AFNetWorkingTestDemo[23484:1105222] 我在test1 3
2017-02-14 10:48:03.313 AFNetWorkingTestDemo[23484:1105222] 我在test1 4
2017-02-14 10:48:03.313 AFNetWorkingTestDemo[23484:1105222] 我在test2 0
2017-02-14 10:48:03.314 AFNetWorkingTestDemo[23484:1105222] 我在test2 1
2017-02-14 10:48:03.314 AFNetWorkingTestDemo[23484:1105222] 我在test2 2
2017-02-14 10:48:03.314 AFNetWorkingTestDemo[23484:1105222] 我在test2 3
2017-02-14 10:48:03.315 AFNetWorkingTestDemo[23484:1105222] 我在test2 4

可以看出test2在 test1完成以後 test2才開始執行

4. dispatch_barrier_async和dispatch_barrier_sync

1.比較
* 共同點:他之前的任務會在他之前完成,他之後的任務會等他在執行完後執行
* 不同點:dispatch_barrier_async後面的任務不會等他執行完再 被添加進 隊列;dispatch_barrier_sync後面的任務會等他再執行完以後再添加 進隊列
* 任務是 先添加進隊列,但是並不是一添加進去就開始執行
2. 例子

//dispatch_barrier_async
 dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我是任務1");
        }
    });
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
        NSLog(@"我是任務2");
        }
    });
    NSLog(@"我在 dispatch_barrier_async之前");
    dispatch_barrier_async(test1, ^{//任務0
        for (int i = 0; i<5; i++) {
            NSLog(@"我是任務0");
        }
    });
    NSLog(@"我在 dispatch_barrier_async之後");//該行代碼會在任務0還沒有執行完就會被執行
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我是任務3");
        }
    });
    dispatch_async(test1, ^{
        for (int i = 0; i<5; i++) {
            NSLog(@"我是任務4");
        }
    });
    //dispatch_barrier_async打印結果如下
    2017-02-14 11:15:32.201 AFNetWorkingTestDemo[23777:1118873] 我是任務1
2017-02-14 11:15:32.201 AFNetWorkingTestDemo[23777:1118830] 我在 dispatch_barrier_async之前
2017-02-14 11:15:32.201 AFNetWorkingTestDemo[23777:1118866] 我是任務2
2017-02-14 11:15:32.202 AFNetWorkingTestDemo[23777:1118873] 我是任務1
2017-02-14 11:15:32.202 AFNetWorkingTestDemo[23777:1118830] 我在 dispatch_barrier_async之後
2017-02-14 11:15:32.202 AFNetWorkingTestDemo[23777:1118866] 我是任務2
2017-02-14 11:15:32.202 AFNetWorkingTestDemo[23777:1118873] 我是任務1
2017-02-14 11:15:32.203 AFNetWorkingTestDemo[23777:1118866] 我是任務2
2017-02-14 11:15:32.204 AFNetWorkingTestDemo[23777:1118873] 我是任務1
2017-02-14 11:15:32.204 AFNetWorkingTestDemo[23777:1118866] 我是任務2
2017-02-14 11:15:32.204 AFNetWorkingTestDemo[23777:1118873] 我是任務1
2017-02-14 11:15:32.205 AFNetWorkingTestDemo[23777:1118866] 我是任務2
2017-02-14 11:15:32.205 AFNetWorkingTestDemo[23777:1118866] 我是任務0
2017-02-14 11:15:32.206 AFNetWorkingTestDemo[23777:1118866] 我是任務0
2017-02-14 11:15:32.207 AFNetWorkingTestDemo[23777:1118866] 我是任務0
2017-02-14 11:15:32.208 AFNetWorkingTestDemo[23777:1118866] 我是任務0
2017-02-14 11:15:32.208 AFNetWorkingTestDemo[23777:1118866] 我是任務0
2017-02-14 11:15:32.208 AFNetWorkingTestDemo[23777:1118866] 我是任務3
2017-02-14 11:15:32.208 AFNetWorkingTestDemo[23777:1118873] 我是任務4
2017-02-14 11:15:32.225 AFNetWorkingTestDemo[23777:1118866] 我是任務3
2017-02-14 11:15:32.225 AFNetWorkingTestDemo[23777:1118873] 我是任務4
2017-02-14 11:15:32.247 AFNetWorkingTestDemo[23777:1118866] 我是任務3
2017-02-14 11:15:32.251 AFNetWorkingTestDemo[23777:1118873] 我是任務4
2017-02-14 11:15:32.251 AFNetWorkingTestDemo[23777:1118866] 我是任務3
2017-02-14 11:15:32.252 AFNetWorkingTestDemo[23777:1118873] 我是任務4
2017-02-14 11:15:32.252 AFNetWorkingTestDemo[23777:1118866] 我是任務3
2017-02-14 11:15:32.254 AFNetWorkingTestDemo[23777:1118873] 我是任務4
//dispatch_barrier_sync
//**如果把代碼中的dispatch_barrier_async變成dispatch_barrier_sync
打印結果如下**
2017-02-14 11:29:22.922 AFNetWorkingTestDemo[23893:1123891] 我是任務1
2017-02-14 11:29:22.922 AFNetWorkingTestDemo[23893:1123881] 我是任務2
2017-02-14 11:29:22.922 AFNetWorkingTestDemo[23893:1123891] 我是任務1
2017-02-14 11:29:22.923 AFNetWorkingTestDemo[23893:1123891] 我是任務1
2017-02-14 11:29:22.923 AFNetWorkingTestDemo[23893:1123891] 我是任務1
2017-02-14 11:29:22.923 AFNetWorkingTestDemo[23893:1123891] 我是任務1
2017-02-14 11:29:22.923 AFNetWorkingTestDemo[23893:1123881] 我是任務2
2017-02-14 11:29:22.922 AFNetWorkingTestDemo[23893:1123797] 我在 dispatch_barrier_sync之前
2017-02-14 11:29:22.924 AFNetWorkingTestDemo[23893:1123881] 我是任務2
2017-02-14 11:29:22.925 AFNetWorkingTestDemo[23893:1123881] 我是任務2
2017-02-14 11:29:22.925 AFNetWorkingTestDemo[23893:1123881] 我是任務2
2017-02-14 11:29:22.926 AFNetWorkingTestDemo[23893:1123797] 我是任務0
2017-02-14 11:29:22.926 AFNetWorkingTestDemo[23893:1123797] 我是任務0
2017-02-14 11:29:22.927 AFNetWorkingTestDemo[23893:1123797] 我是任務0
2017-02-14 11:29:22.927 AFNetWorkingTestDemo[23893:1123797] 我是任務0
2017-02-14 11:29:22.928 AFNetWorkingTestDemo[23893:1123797] 我是任務0
2017-02-14 11:29:22.928 AFNetWorkingTestDemo[23893:1123797] 我在 dispatch_barrier_sync之後//會在任務0執行完再打印
2017-02-14 11:29:22.929 AFNetWorkingTestDemo[23893:1123891] 我是任務4
2017-02-14 11:29:22.929 AFNetWorkingTestDemo[23893:1123891] 我是任務4
2017-02-14 11:29:22.929 AFNetWorkingTestDemo[23893:1123881] 我是任務3
2017-02-14 11:29:22.930 AFNetWorkingTestDemo[23893:1123891] 我是任務4
2017-02-14 11:29:22.930 AFNetWorkingTestDemo[23893:1123891] 我是任務4
2017-02-14 11:29:22.930 AFNetWorkingTestDemo[23893:1123891] 我是任務4
2017-02-14 11:29:22.930 AFNetWorkingTestDemo[23893:1123881] 我是任務3
2017-02-14 11:29:22.931 AFNetWorkingTestDemo[23893:1123881] 我是任務3
2017-02-14 11:29:22.932 AFNetWorkingTestDemo[23893:1123881] 我是任務3
2017-02-14 11:29:22.932 AFNetWorkingTestDemo[23893:1123881] 我是任務3

從上面的例子中可以看出這兩個的區別:其實不難理解,因為 async 是一部的,所以不會阻塞線程,但是 sync 會阻塞線程,所以同步和異步的區別是在這裏體現的

5. dispatch_group_async(group ,queue,block)

  1. 會等添加進 group 中的所有任務執行完再執行 dispatch_group_notify的任務,如果我們想要任務3在任務1和2執行完後再執行,就把1和2放到同一個組裏,例子如下
    dispatch_group_t group =  dispatch_group_create();//創建一個組
        //將任務1加入 group 中
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務1-1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"任務1-2");
    });
    //將任務2加入 group 中
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務2-1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"任務2-2");
    });
 dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務3-1");
        [NSThread sleepForTimeInterval:3];
        NSLog(@"任務3-2");
    });
    //結果如下
    2017-02-14 11:44:49.912 AFNetWorkingTestDemo[24094:1132147] 任務1-1
2017-02-14 11:44:49.912 AFNetWorkingTestDemo[24094:1132138] 任務2-1
2017-02-14 11:44:52.912 AFNetWorkingTestDemo[24094:1132147] 任務1-2
2017-02-14 11:44:52.912 AFNetWorkingTestDemo[24094:1132138] 任務2-2
2017-02-14 11:44:52.914 AFNetWorkingTestDemo[24094:1132138] 任務3-1//任務3等到任務1和2執行完再執行
2017-02-14 11:44:55.916 AFNetWorkingTestDemo[24094:1132138] 任務3-2
  1. dispatch_group_async和dispatch_barrier_async 對比

    • 都會在其前面的任務完成之後再執行
    • 但是dispatch_group_async得後面的任務是在他執行完後再執行
      dispatch_barrier_async中沒有加入組中的任務是在所有組中的任務完成以後再執行
  2. dispatch_group_wait和 dispatch_group_noticy

    • dispatch_group_wait可以指定在 group 的任務再執行了幾秒時候做某些反應


    //第一個參數,開始時間、第二個參數當 group 中的任務執行多少面開始做某些事情,要是設置為DISPATCH_TIME_FOREVER,則會等 group 中的處理完再做,返回值為0,說明處理完,不為0,說明沒處理完
    dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW,DISPATCH_TIME_FOREVER);
    long result = dispatch_group_wait(group, timeout);
    >

  3. dispatch_group_noticy在 group 中的處理完再做

6:dispatch_semaphore_t:信號量機制

  1. 要進行操作前會判斷semaphore的計數是不是大於等於1,是的話先讓信號量計數-1,然後執行操作,操作執行完成後,再讓信號量+1
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);//設置信號量初始值為1
    NSMutableArray *array = [NSMutableArray array];
    for (int i = 0; i<5; i++) {
    //之要信號量值不大於等於1,就會一直等待,知道>=1,再對數組進行操作.
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [array addObject:[NSString stringWithFormat:@"%d",i]];
        //對數組進行完操作,讓信號量計數+1,這樣下次有線程要訪問,就可以訪問
        dispatch_semaphore_signal(semaphore);
    }

相當於操作系統中的 wait-sigal 操作,那個數組就是一個共享資源,只要有人在用(當 信號量計數為1),別的就會陷入等待,直到別人不用了,再去用,用之前先讓計數-1,這樣當別的人來用,就會知道有人在用,我還是等待吧,用完後,要記得讓計數+1,釋放這個資源,告訴別人這個資源我已經不用了,你可以用了**因為####7. dispatch_apply:讓代碼重復執行

//第一個參數是重復執行的次數,
//第二個參數是一個隊列,
//第三個參數是下標,用來區分不同的 block
dispatch_apply(5, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"%d",index);
    });

應用:可以利用他來遍歷數組

8. dispatch_once;


  1. 讓代碼在應用程序的生命周期中只執行一次
    可以用來實現單例,線程安全


static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
>

9. dispatch_suspend(Queue)和 dispatch_resume(Queue);

dispatch_suspend掛起一個尚未執行的 queue:如果這個 queue 已經在執行,沒有影響,要是沒有執行就停止執行
dispatch_resume讓被掛起的 queue 繼續執行。

總結

  1. dispatch_queue_create(“queue 名字”,queue的屬性);
    • 第二個參數為 NULL:創建的是串行隊列
    • 第二個參數為:DISPATCH_QUEUE_CONCURRENT,是並發隊列
  2. dispatch_async:創建異步任務,會開啟新線程,不會阻塞主線程
  3. dispatch_sync:創建同步任務,不會開啟新線程,會阻塞主線程、造成 死鎖
  4. dispatch_set_target_queue(“要修改優先級的隊列”,“參照物”):修改隊列的優先級/通過將多個隊列設置到同一個目標隊列上,可以讓這些隊列同步運行
  5. dispatch_barrier_async()和dispatch_barrier_async()都會等他們前面的任務執行完再執行,他們後面的任務會在他們執行完再執行,區別在於 async 後面的任務不會等到其執行完再加入 queue,但是 sync 會
  6. dispatch_group_async:會等組裏面的任務都執行完了,再去執行dispatch_group_noticy中的任務
  7. dispatch_semaphore_t:信號量機制,保證線程操作安全,確保一次只有一個在操作共享資源,在操作前調用dispatch_semaphore_wait 申請資源,讓計數-1,操作完要調用dispatch_semaphore_signal()釋放資源,計數+1
  8. dispatch_once:讓block 中代碼在應用程序的生命周期裏面只執行一次
  9. dispatch_suspend:掛起為執行的 queue,對於已經開始執行的 queue 沒有作用
  10. dispatch_resume:讓被掛起的 queue 恢復執行

Block 如何被添加進 Dispatch queue 中


block 並不是直接被添加進 Queue 中的,而是會先被添加進一個 Dispatch Continuation 的結構體中,這個結構體用於保存 block 所屬的 group 以及其它一些信息。


block在 Queue 中如何執行


當 Global Diapatch Queue 開始執行 block 時,
1. libdispatch 會先從 Global Diapatch的自身的 FIFO 隊列中取出 dispatch Continuation,
2. 然後調用 pthread_workqueue_additem_np 函數,將該 Global Disapatch 自身以及符合其優先級的workqueue 還有 dispatch Conitunation 的回調函數作為參數傳遞過去
3. pthread_workqueue_additem_np 使用 wokq_kernreturn 系統調用,通知 workqueue 增加應當執行的項目
4. XNU 內核根據該通知確定要不要生成線程,如果是 Overcommite 優先級的,則始終生成線程(優先級中有Overcommite使用在串行隊列中,所以會始終生成線程)
5. workqueue 的線程執行 pthread_workqueue 函數,該函數調用libdispatch的回調函數,在該回調函數中執行加入到dispatch Continuation,的 block
6. block 執行結束後,進行通知 dispatch group 結束,釋放dispatch Continuation, 準備下一個 block。


Tags: 上下文 多線程 開發 如何 程序

文章來源:


ads
ads

相關文章
ads

相關文章

ad