全面詳細的GCD用法講解

線程概念

iOS程序中,主線程(又叫作UI線程)主要任務是處理UI事件,顯示和刷新UI,(只有主線程有直接修改UI的能力)耗時的操作放在子線程(又叫作后臺線程、異步線程)。在iOS中開子線程去處理耗時的操作,可以有效提高程序的執行效率,提高資源利用率。但是開啟線程會占用一定的內存,(主線程的堆棧大小是1M,第二個線程開始都是512KB,并且該值不能通過編譯器開關或線程API函數來更改)降低程序的性能。所以一般不要同時開很多線程。

三種多線程變成的優缺點比較

  1. NSThread (抽象層次:低)

優點:輕量級,簡單易用,可以直接操作線程對象
缺點: 需要自己管理線程的生命周期,線程同步。線程同步對數據的加鎖會有一定的系統開銷。

2.NSOperation (抽象層次:中)

優點:不需要關心線程管理,數據同步的事情,可以把精力放在學要執行的操作上。基于GCD,是對GCD 的封裝,比GCD更加面向對象
缺點: NSOperation是個抽象類,使用它必須使用它的子類,可以實現它或者使用它定義好的兩個子類NSInvocationOperation、NSBlockOperation.

3.GCD 全稱Grand Center Dispatch (抽象層次:高)

優點:是 Apple 開發的一個多核編程的解決方法,簡單易 用,效率高,速度快,基于C語言,更底層更高效,并且不是Cocoa框架的一部分,自動管理線程生命周期(創建線程、調度任務、銷毀線程)。
缺點: 使用GCD的場景如果很復雜,就有非常大的可能遇到死鎖問題。

隊列

GCD編程的核心就是dispatch隊列,dispatch block的執行最終都會放進某個隊列中去進行

  1. The main queue(主線程串行隊列):
    與主線程功能相同,提交至 Main queue 的任務會在主線程中執行,
    Main queue 可以通過 dispatch_get_main_queue() 來獲取。
  2. Global queue(全局并發隊列):
    全局并發隊列由整個進程共享,有高、中(默認)、低、后臺四個優先級別。
    Global queue 可以通過調用 dispatch_get_global_queue 函數來獲取(可以設置優先級)
  3. Custom queue (自定義隊列):
    可以為串行,也可以為并發。
    Custom queue 可以通過 dispatch_queue_create() 來獲取;
  4. Group queue(隊列組):
    將多線程進行分組,最大的好處是可獲知所有線程的完成情況。
    Group queue 可以通過調用dispatch_group_create()來獲取

執行

執行任務有兩種方式:同步執行和異步執行。兩者的主要區別是:是否具備開啟新線程的能力

  • 同步執行(sync):只能在當前線程中執行任務,不具備開啟新線程的能力
  • 異步執行(async):可以在新的線程中執行任務,具備開啟新線程的能力

隊列和執行方式的組合,以及是都會開辟新的線程,用表格來表示一下一目了然

并發隊列 串行隊列 主隊列
同步(sync) 沒有開啟新線程,串行執行任務 沒有開啟新線程,串行執行任務 死鎖
異步(async) 有開啟新線程,并發執行任務 有開啟新線程(1條),串行執行任務 沒有開啟新線程,串行執行任務

The main queue

//獲取主線程串行隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

//主線程串行隊列同步執行任務,在主線程運行時,會產生死鎖
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue,^{
NSLog("MainQueue");            
});

//主線程串行隊列異步執行任務,在主線程運行,不會產生死鎖
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue,^{
NSLog("MainQueue");            
});

Global queue(全局并發隊列)

獲取全局并發隊列

//程序默認的隊列級別,一般不要修改,DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//HIGH
dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//LOW
dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//BACKGROUND
dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

//全局并發隊列同步執行任務,在主線程執行,會導致頁面卡頓。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_sync(globalQueue, ^{
    sleep(2.0);
    NSLog(@"sleep 2.0s");
});
NSLog(@"next task");

//控制臺打印

2015-11-18 15:51:45.550 Whisper[33152:345023] current task
2015-11-18 15:51:47.552 Whisper[33152:345023] sleep 2.0s
2015-11-18 15:51:47.552 Whisper[33152:345023] next task

全局并發隊列異步執行任務,在主線程運行,會開啟新的子線程去執行任務,頁面不會卡頓

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
    sleep(2.0);
    NSLog(@"sleep 2.0s");
});
NSLog(@"next task");

//控制臺輸出

2015-11-18 15:50:14.999 Whisper[33073:343781] current task
2015-11-18 15:50:15.000 Whisper[33073:343781] next task
2015-11-18 15:50:17.004 Whisper[33073:343841] sleep 2.0s

全局并發隊列,異步執行多個任務。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
    NSLog(@"先加入隊列");
});
dispatch_async(globalQueue, ^{
    NSLog(@"后加入隊列");
});
NSLog(@"next task");

控制臺輸出如下:

2015-11-18 16:54:52.202 Whisper[39827:403208] current task
2015-11-18 16:54:52.203 Whisper[39827:403208] next task
2015-11-18 16:54:52.205 Whisper[39827:403309] 先加入隊列
2015-11-18 16:54:52.205 Whisper[39827:403291] 后加入隊列

異步線程的執行順序是不確定的。幾乎同步開始執行
全局并發隊列由系統默認生成的,所以無法調用dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。

Custom queue (自定義隊列)

dispatch_time(dispatch_time_t when, int64_t delta);

參數注釋:
第一個參數一般是DISPATCH_TIME_NOW,表示從現在開始
第二個參數是延時的具體時間
延時1秒可以寫成如下幾種:
NSEC_PER_SEC----每秒有多少納秒
dispatch_time(DISPATCH_TIME_NOW, 1NSEC_PER_SEC);
USEC_PER_SEC----每秒有多少毫秒(注意是指在納秒的基礎上)
dispatch_time(DISPATCH_TIME_NOW, 1000
USEC_PER_SEC); //SEC---毫秒
NSEC_PER_USEC----每毫秒有多少納秒。
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC*NSEC_PER_USEC);SEC---納秒

獲取自定義串行隊列

//第一個參數是給這個queue起的標識,這個在調試的可以看到是哪個隊列在執行,或者在crash日志中,也能做為提示。第二個是需要創建的隊列類型,是串行的還是并發的
dispatch_queue_t serialQueue = dispatch_queue_create("com.customSerial.queue", DISPATCH_QUEUE_SERIAL);

獲取自定義并發隊列

dispatch_queue_t conCurrentQueue =   dispatch_queue_create("com.conCurrentQueue.queue", DISPATCH_QUEUE_CONCURRENT);

Group queue (隊列組)

回到主線程更新UI
在iOS開發過程中,我們一般在主線程里邊進行UI刷新,例如:點擊、滾動、拖拽等事件。我們通常把一些耗時的操作放在其他線程,比如說圖片下載、文件上傳等耗時操作。而當我們有時候在其他線程完成了耗時操作時,需要回到主線程

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 2; ++i) {
        NSLog(@"1------%@",[NSThread currentThread]);
    }

    // 回到主線程
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2-------%@",[NSThread currentThread]);
    });
});

隊列組
當遇到需要執行多個線程并發執行,然后等多個線程都結束之后,再做下一步操作,我們可以用隊列組

// 全局并行隊列  
 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);  
dispatch_group_t groupQueue = dispatch_group_create();
NSLog(@"current task");
dispatch_group_async(groupQueue, globalQueue, ^{
   NSLog(@"耗時操作1");
});
dispatch_group_async(groupQueue, globalQueue, ^{
   NSLog(@"耗時操作2");
});
dispatch_group_notify(groupQueue, dispatch_get_main_queue(), ^{
   NSLog(@"groupQueue中的任務 都執行完成,回到主線程更新UI");
});
NSLog(@"next task");

控制臺打印

2015-11-19 13:47:55.117 Whisper[1645:97116] current task
2015-11-19 13:47:55.117 Whisper[1645:97116] next task
2015-11-19 13:47:55.119 Whisper[1645:97178] 耗時操作1
2015-11-19 13:47:55.119 Whisper[1645:97227] 耗時操作2
2015-11-19 13:47:55.171 Whisper[1645:97116] groupQueue中的任務 都執行完成,回到主線程更新UI

延時添加隊列

dispatch_time_t delayTime3 = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
dispatch_time_t delayTime2 = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"current task");
dispatch_after(delayTime3, mainQueue, ^{
  NSLog(@"3秒之后添加到隊列");
});
dispatch_after(delayTime2, mainQueue, ^{
   NSLog(@"2秒之后添加到隊列");
});
NSLog(@"next task");

控制臺打印

2015-11-19 15:50:19.369 Whisper[2725:172593] current task
2015-11-19 15:50:19.370 Whisper[2725:172593] next task
2015-11-19 15:50:21.369 Whisper[2725:172593] 2秒之后添加到隊列
2015-11-19 15:50:22.654 Whisper[2725:172593] 3秒之后添加到隊列

dispatch_after只是延時提交block,并不是延時后立即執行,不能做到精確控制

柵欄方法
我們有時需要異步執行兩組操作,而且第一組操作執行完之后,才能開始執行第二組操作。這樣我們就需要一個相當于柵欄一樣的一個方法將兩組異步執行的操作組給分割起來,當然這里的操作組里可以包含一個或多個任務。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄。

dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });

控制臺打印

2016-09-03 19:35:51.271 GCD[11750:1914724] ----1-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----2-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----barrier-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914722] ----3-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914724] ----4-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}

一次性代碼(只執行一次) dispatch_once
我們在創建單例、或者有整個程序運行過程中只執行一次的代碼時,我們就用到了GCD的dispatch_once方法。使用dispatch_once函數能保證某段代碼在程序運行過程中只被執行1次。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執行1次的代碼(這里面默認是線程安全的)
});

**快速迭代方法 **
通常我們會用for循環遍歷,但是GCD給我們提供了快速迭代的方法dispatch_apply,使我們可以同時遍歷。比如說遍歷0~5這6個數字,for循環的做法是每次取出一個元素,逐個遍歷。dispatch_apply可以同時遍歷多個數字。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(6, queue, ^(size_t index) {
    NSLog(@"%zd------%@",index, [NSThread currentThread]);
});

控制臺打印

2016-09-03 19:37:02.250 GCD[11764:1915764] 1------<NSThread: 0x7fac9a7029e0>{number = 1, name = main}
2016-09-03 19:37:02.250 GCD[11764:1915885] 0------<NSThread: 0x7fac9a614bd0>{number = 2, name = (null)}
2016-09-03 19:37:02.250 GCD[11764:1915886] 2------<NSThread: 0x7fac9a542b20>{number = 3, name = (null)}
2016-09-03 19:37:02.251 GCD[11764:1915764] 4------<NSThread: 0x7fac9a7029e0>{number = 1, name = main}
2016-09-03 19:37:02.250 GCD[11764:1915884] 3------<NSThread: 0x7fac9a76ca10>{number = 4, name = (null)}
2016-09-03 19:37:02.251 GCD[11764:1915885] 5------<NSThread: 0x7fac9a614bd0>{number = 2, name = (null)}

dispatch_apply在給定的隊列上多次執行某一任務

  • dispatch_apply函數的功能:把一項任務提交到隊列中多次執行,隊列可以是串行也可以是并行,dispatch_apply不會立刻返回,在執行完block中的任務后才會返回,是同步執行的函數。
  • dispatch_apply正確使用方法:為了不阻塞主線程,一般把dispatch_apply放在異步隊列中調用,然后執行完成后通知主線程
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
dispatch_queue_t applyQueue = dispatch_get_global_queue(0, 0);
//第一個參數,3--block執行的次數
//第二個參數,applyQueue--block任務提交到的隊列
//第三個參數,block--需要重復執行的任務
dispatch_apply(3, applyQueue, ^(size_t index) {
      NSLog(@"current index %@",@(index));
      sleep(1);
});
NSLog(@"dispatch_apply 執行完成");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
      NSLog(@"回到主線程更新UI");
});
});
NSLog(@"next task");

控制臺打印

2015-11-19 16:24:45.015 Whisper[4034:202269] current task
2015-11-19 16:24:45.016 Whisper[4034:202269] next task
2015-11-19 16:24:45.016 Whisper[4034:202347] current index 0
2015-11-19 16:24:45.016 Whisper[4034:202344] current index 1
2015-11-19 16:24:45.016 Whisper[4034:202345] current index 2
2015-11-19 16:24:46.021 Whisper[4034:202347] dispatch_apply 執行完成
2015-11-19 16:24:46.021 Whisper[4034:202269] 回到主線程更新UI

嵌套使用dispatch_apply會導致死鎖。

dispatch semaphore信號計數器,可以進行更細微的控制
信號計數器,可以進行更細微的控制,類似于馬路信號的手旗,可通過時舉起手旗,不可通過放下手旗。技術信號為0表示等待,技術信號大于等于1減去1而不等待。

假設我們有如下場景,需要對可變數組存入10000個對象,并且在存入之前要做do something處理,do somthing是個很耗時的操作。所以可以使用多線程異步并發的操作:

dispatch_queue_t queue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);


NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];

for (int i = 0; i < 10000; ++i) {

    dispatch_async(queue, ^{

        /*
          do something
         */

        [array addObject:[NSNumber numberWithInt:i]];

    });

}

使用了上面的操作,可以異步并發的執行添加對象到數組,可以明顯提高效率。但是,同一時刻操作同一塊array的內存空間會導致內存問題,出現資源競爭,于是對代碼進行如下優化:

dispatch_queue_t queue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [NSMutableArray arrayWithCapacity:1];

for (int i = 0; i < 10000; ++i) {

    dispatch_async(queue, ^{

        /*
          do something
         */

        //信號==0等待,>=1減1不等待進入下一步代碼
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        [array addObject:[NSNumber numberWithInt:i]];

        //信號 +1
        dispatch_semaphore_signal(semaphore);

    });

}

dispatch_suspend/dispatchp_resume

// 掛起指定隊列
dispatch_suspend(queue);
// 恢復指定隊列
dispatchp_resume(queue);

  dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
    //提交第一個block,延時5秒打印。
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds...");
    });
    //提交第二個block,也是延時5秒打印
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds again...");
    });
    //延時一秒
    NSLog(@"sleep 1 second...");
    sleep(1);
    //掛起隊列
    NSLog(@"suspend...");
    dispatch_suspend(queue);
    //延時10秒
    NSLog(@"sleep 10 second...");
    sleep(10);
    //恢復隊列
    NSLog(@"resume...");
    dispatch_resume(queue);

打印輸出

  dispatch_queue_t queue = dispatch_queue_create("com.test.gcd", DISPATCH_QUEUE_SERIAL);
    //提交第一個block,延時5秒打印。
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds...");
    });
    //提交第二個block,也是延時5秒打印
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"After 5 seconds again...");
    });
    //延時一秒
    NSLog(@"sleep 1 second...");
    sleep(1);
    //掛起隊列
    NSLog(@"suspend...");
    dispatch_suspend(queue);
    //延時10秒
    NSLog(@"sleep 10 second...");
    sleep(10);
    //恢復隊列
    NSLog(@"resume...");
    dispatch_resume(queue);

AFNetworking+GCD處理并發問題

GCD的leave和enter 我們利用dispatch_group_t創建隊列組,手動管理group關聯的block運行狀態,進入和退出group的次數必須匹配。
//1.創建隊列組  
    dispatch_group_t group = dispatch_group_create();  
    //2.創建隊列  
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
    //3.添加請求  
    dispatch_group_async(group, queue, ^{  
        dispatch_group_enter(group);  
        [HomeRequest getPointBuyAllConfigurationStrategyType:_dataType success:^(NSInteger code, NSDictionary *dict) {  
            dispatch_group_leave(group);  
        } failuer:^(NSInteger code, NSString *message) {  
            dispatch_group_leave(group);  
        }];  
    });  
    dispatch_group_async(group, queue, ^{  
        dispatch_group_enter(group);  
        [HomeRequest getStockLeverRiskStockCode:_buyingStrategyModel.stockCode strategyType:_dataType success:^(NSInteger code, NSDictionary *dict) {  
            dispatch_group_leave(group);  
        } failuer:^(NSInteger code, NSString *message) {  
            dispatch_group_leave(group);  
        }];  
    });  
    //4.隊列組所有請求完成回調刷新UI  
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{  
        NSLog(@"model:%f",_buyingStrategyModel.leverrisk);  
    });  
GCD的信號量dispatch_semaphore_t這種方式有點類似于通知模式,是利用監聽信號量來發送消息以達到并發處理的效果
//創建信號量  
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);  
      
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
    dispatch_group_t group = dispatch_group_create();  
      
    dispatch_group_async(group, queue, ^{  
        [HomeRequest getPointBuyAllConfigurationStrategyType:_dataType success:^(NSInteger code, NSDictionary *dict) {  
            dispatch_semaphore_signal(semaphore);  
        } failuer:^(NSInteger code, NSString *message) {  
            dispatch_semaphore_signal(semaphore);  
        }];  
    });  
    dispatch_group_async(group, queue, ^{  
        [HomeRequest getStockLeverRiskStockCode:_buyingStrategyModel.stockCode strategyType:_dataType success:^(NSInteger code, NSDictionary *dict) {  
            dispatch_semaphore_signal(semaphore);  
        } failuer:^(NSInteger code, NSString *message) {  
            dispatch_semaphore_signal(semaphore);  
        }];  
    });  
    dispatch_group_notify(group, queue, ^{  
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);  
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);  
        NSLog(@"信號量為0");  
    });  

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

推薦閱讀更多精彩內容