iOS多線程GCD篇

首先,GCD的源碼在這Grand Central Dispatch,若是想要深刻的理解GCD的實現原理,最好仍是下載一份源碼慢慢的閱讀一下。 本文不會對GCD的底層源碼進行剖析,只會總結一下應用層面的東西。 本文會涉及到的內容:git

  • 什麼是GCD
  • GCD的基礎實現
  • GCD與其餘多線程實現方式相比的優劣
  • GCD經常使用API的釋義、解析與應用
  • GCD的一些坑

一. 什麼是GCD

GCD的全稱是Grand Central Dispatch,是蘋果公司開發的多核心處理器編寫併發運行的程序。在Mac OS X10.6 和iOS4以及以上的系統可使用。它是一套底層API,提供了一種新的方法來進行併發程序編寫。從基本功能上講,GCD有點像NSOperationQueue,他們都容許程序將任務切分爲多個單一任務而後提交至工做隊列來併發地或者串行地執行。GCD比之NSOpertionQueue更底層更高效,而且它不是Cocoa框架的一部分。 除了代碼的平行執行能力,GCD還提供高度集成的事件控制系統。能夠設置句柄來響應文件描述符、mach ports(Mach port 用於 OS X上的進程間通信)、進程、計時器、信號、用戶生成事件。這些句柄經過GCD來併發執行。github

蘋果官方對GCD的解釋中有這樣一句話:編程

開發者要作的只是定義執行的任務並追加到適當的 Dispatch Queue 中。swift

因此在GCD中咱們須要作的只是兩件事:1:定義任務;2:把任務加到隊列中。後端

  • GCD容許程序將任務切分紅多個任務而後提交到一個隊列中併發執行或者串行執行。
  • GCD是很是高效的多線程開發方式。而且它並非Cocoa框架的一部分。

GCD編程的核心就是dispatch隊列,dispatch block的執行最終都會放進某個隊列中去進行,下邊是GCD幾種隊列獲取方式:緩存

  1. 主線程隊列(The main queue):提交至main queue的任務會在主線程中執行。
    • 能夠調用dispatch_get_main_queue()來得到。
    • 由於main queue是與主線程相關的,因此這是一個串行隊列。和UI相關的修改必須使用Main Queue。
  2. 全局併發隊列(Global queue): 全局併發隊列由整個進程共享,有高、中(默認)、低、後臺四個優先級別。
    • 能夠經過調用dispatch_get_global_queue函數來獲取(能夠設置優先級)
  3. 自定義隊列(Custom queue):
    • 並行隊列(Concurrent Dispatch Queue):全局隊列是併發隊列,並由整個進程共享。
      • 能夠併發的執行多個任務,可是執行完成的順序是隨機的。
      • 進程中存在三個全局隊列:高、中(默認)、低三個優先級隊列。能夠調用dispatch_get_global_queue函數傳入優先級來訪問隊列。
    • 串行隊列(Serial Dispatch Queue):也叫作用戶隊列(GCD並不這樣稱呼這種隊列, 可是沒有一個特定的名字來形容這種隊列,因此咱們稱其爲用戶隊列)。
      • 同一時間只執行一個任務,一般用於同步訪問特定的資源或數據。
      • 當你建立多個串行隊列的時候,每一個隊列裏的任務都是串行執行的
      • 每一個串行隊列與隊列之間是併發執行的。
      • 有點像傳統線程中的mutex。
  4. Group queue (隊列組):將多線程進行分組,最大的好處是可獲知全部線程的完成狀況。
    • Group queue 能夠經過調用dispatch_group_create()來獲取,經過 dispatch_group_notify,能夠直接監聽組裏全部線程完成狀況。

因此,在使用GCD的時候咱們只須要搞清楚咱們的任務是要同步執行仍是異步執行,是要串行仍是並行,咱們並不須要直接和線程打交道,只須要向建立好的隊列中添加須要被執行的代碼塊就能夠了。除了提供代碼併發執行的能力,還提供了高度集成的事件控制系統。GCD本身在後端管理着一個可調度線程池,不只決定着你的代碼塊將在哪一個線程被執行,它還根據可用的系統資源對這些線程進行管理。這樣就能夠將開發者從線程管理的工做中解放出來,而且經過線程的集中管理,緩解了線程被大量建立的問題。 GCD的API很大程度是基於block,固然也能夠脫離block來使用,好比使用函數指針和上下文指針等。實踐證實配合block使用時,GCD很是的簡單易用且能發揮最大的能力。安全

二. GCD的基礎實現

GCD的基本概念就是dispatch queue。dispatch queue是一個對象,它能夠接受任務,並將任務以先到先執行的順序來執行。也就是FIFO隊列。dispatch queue能夠是併發的或串行的。併發任務會像NSOperationQueue那樣基於系統負載來合適地併發進行,串行隊列同一時間只執行單一任務。 GCD是純c語言的,但它被組建成面向對象的風格。GCD對象被稱爲dispatch object。Dispatch object像Cocoa對象同樣是引用計數的。使用dispatch_release和dispatch_retain函數來操做dispatch object的引用計數來進行內存管理。 在iOS 6.0之前,咱們必須手動管理GCD對象的內存,使用(dispatch_retain,dispatch_release),ARC並不會去管理它們。可是iOS6.0之後就不須要了,ARC已經可以管理GCD對象了,這時候,GCD對象就如同普通的OC對象同樣,不該該使用dispatch_retain ordispatch_release。bash

FIFO:網絡

FIFO隊列稱爲dispatch queue,FIFO就是 First In First Out的簡稱,翻譯過來就是先進先出。GCD保證先進來的任務先獲得執行。多線程

三. GCD與其餘多線程實現方式相比的優劣

GCD的優點:

  1. GCD很優雅,輕巧。比專門建立消耗資源的線程更加的實用且快速。
  2. GCD自動根據系統的負載來增減線程的數量,這就增長了計算效率。
  3. GCD自動根據系統負載來增減線程數量,這就減小了上下文切換以及增長了計算效率。
  4. GCD 能經過推遲昂貴計算任務並在後臺運行它們來改善你的應用的響應性能。
  5. GCD 提供一個易於使用的併發模型而不只僅只是鎖和線程,以幫助咱們避開併發陷阱。
  6. GCD 具備在常見模式(例如單例)上用更高性能的原語優化你的代碼的潛在能力。
  7. GCD 會自動利用更多的CPU
  8. 只須要告訴 GCD 想要執行什麼任務,不須要編寫任何線程管理代碼
  9. GCD 會自動管理線程的生命週期(建立線程、調度任務、銷燬線程)

GCD提供不少超越傳統多線程編程的優點:

  1. GCD比之NSThread跟簡單易用。因爲GCD基於work unit而非像thread那樣基於運算,因此GCD能夠控制諸如等待任務結束、監視文件描述符、週期執行代碼以及工做掛起等任務。基於block的血統致使它能極爲簡單得在不一樣代碼做用域之間傳遞上下文。
  2. 相對於NSOperation來講GCD更接近底層,而NSOperation則是更高級抽象,因此GCD在追求性能的底層操做來講,是速度最快的。
  3. GCD須要手動實現異步操做之間的事務性,順序行,依賴關係等,而NSOperationQueue已經內建了這些支持
  4. 若是異步操做的過程須要更多的被交互和UI呈現出來,NSOperation會是一個更好的選擇。
  5. 若是須要執行的任務相互的依賴關係較輕,而且須要高併發能力的話,GCD則更有優點。
  6. 在NSOperationQueue中,咱們能夠隨時取消已經設定要準備執行的任務(固然,已經開始的任務就沒法阻止了),而GCD無法中止已經加入queue的block(實際上是有的,但須要許多複雜的代碼);
  7. 咱們能將KVO應用在NSOperation中,能夠監聽一個Operation是否完成或取消,這樣子能比GCD更加有效地掌控咱們執行的後臺任務;
  8. 在NSOperation中,咱們可以設置NSOperation的priority優先級,可以使同一個並行隊列中的任務區分前後地執行,而在GCD中,咱們只能區分不一樣任務隊列的優先級,若是要區分block任務的優先級,也須要大量的複雜代碼;

總的來講,Operation queue 提供了更多你在編寫多線程程序時須要的功能,並隱藏了許多線程調度,線程取消與線程優先級的複雜代碼,爲咱們提供簡單的API入口。從編程原則來講,通常咱們須要儘量的使用高等級、封裝完美的API,在必須時才使用底層API。可是我認爲當咱們的需求可以以更簡單的底層代碼完成的時候,簡潔的GCD或許是個更好的選擇,而Operation queue 爲咱們提供能更多的選擇。

四. GCD經常使用API的釋義、解析與應用

接下來就是介紹一下GCD經常使用的API、釋義、解析以及應用

1. 建立和獲取

GCD手動建立隊列部分

// 手動建立隊列API
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
// 建立串行隊列
dispatch_queue_t queue = dispatch_queue_create("com.sanyucz.queue", DISPATCH_QUEUE_SERIAL);
// 建立並行隊列
dispatch_queue_t queue = dispatch_queue_create("com.sanyucz.queue", DISPATCH_QUEUE_CONCURRENT);
複製代碼

手動建立隊列API有兩個可變參數:

  1. const char ***label,爲隊列指定一個名稱
  2. dispatch_queue_attr_t attr,關於這個參數官方API有直接的說明:
/*!
* @const DISPATCH_QUEUE_SERIAL
* @discussion A dispatch queue that invokes blocks serially in FIFO order.
*/
#define DISPATCH_QUEUE_SERIAL NULL
/*!
* @const DISPATCH_QUEUE_CONCURRENT
* @discussion A dispatch queue that may invoke blocks concurrently and supports
* barrier blocks submitted with the dispatch barrier API.
*/
#define DISPATCH_QUEUE_CONCURRENT \
      DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \
      _dispatch_queue_attr_concurrent)
複製代碼

ISPATCH_QUEUE_SERIAL 建立一個遵循FIFO協議的串行隊列 DISPATCH_QUEUE_CONCURRENT 建立一個併發隊列

串行隊列實例代碼:

dispatch_queue_t currentQueue = dispatch_queue_create("com.serial.test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(currentQueue, ^{
        for (int i = 0; i < 100; i ++) {
            NSLog(@"串行隊列異步事件:%tu ---",i);
        }
    });
    
dispatch_sync(currentQueue, ^{
        for (int i = 0; i < 100; i ++) {
            NSLog(@"串行隊列同步事件:%tu +++",i);
        }
    });
複製代碼

並行隊列實例代碼:

dispatch_queue_t currentQueue = dispatch_queue_create("com.parallel.test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(currentQueue, ^{
        for (int i = 0; i < 100; i ++) {
            NSLog(@"串行隊列異步事件:%tu ---",i);
        }
    });
    
dispatch_sync(currentQueue, ^{
        for (int i = 0; i < 100; i ++) {
            NSLog(@"串行隊列同步事件:%tu +++",i);
        }
    });
複製代碼

上邊說了一種建立併發隊列的方式,GCD還有另一種獲取併發隊列的方式,這種方式是蘋果已經爲咱們寫好的,咱們只須要傳入參數就能夠了:

dispatch_queue_t concurrent_queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
複製代碼

下邊是官方API:

/*!
 * @function dispatch_get_global_queue
 *
 * @abstract
 * Returns a well-known global concurrent queue of a given quality of service
 * class.
 *
 * @discussion
 * The well-known global concurrent queues may not be modified. Calls to
 * dispatch_suspend(), dispatch_resume(), dispatch_set_context(), etc., will
 * have no effect when used with queues returned by this function.
 *
 * @param identifier
 * A quality of service class defined in qos_class_t or a priority defined in
 * dispatch_queue_priority_t.
 *
 * It is recommended to use quality of service class values to identify the
 * well-known global concurrent queues:
 *  - QOS_CLASS_USER_INTERACTIVE
 *  - QOS_CLASS_USER_INITIATED
 *  - QOS_CLASS_DEFAULT
 *  - QOS_CLASS_UTILITY
 *  - QOS_CLASS_BACKGROUND
 *
 * The global concurrent queues may still be identified by their priority,
 * which map to the following QOS classes:
 *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
 *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
 *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
 *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND
 *
 * @param flags
 * Reserved for future use. Passing any value other than zero may result in
 * a NULL return value.
 *
 * @result
 * Returns the requested global queue or NULL if the requested global queue
 * does not exist.
 */
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_CONST DISPATCH_WARN_RESULT DISPATCH_NOTHROW
dispatch_queue_t
dispatch_get_global_queue(long identifier, unsigned long flags);
複製代碼

能夠看到文檔對兩個參數都作了解釋,其中第一個參數是標識隊列的優先級,一共有四種:

#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就OK了。

獲取主隊列

dispatch_queue_t queue = dispatch_get_main_queue();
複製代碼

2. 同步和異步

下邊是系統提供的同步和異步API:

dispatch_sync(dispatch_queue_t  _Nonnull queue, ^(void)block);
dispatch_async(dispatch_queue_t  _Nonnull queue, ^(void)block);
複製代碼

兩個參數都相同,第一個是須要執行的隊列,第二個是block回調。

同步函數就是等待任務的執行,等任務執行完成後再返回。因此同步就意味着在當前線程中執行任務,並不會開啓新的線程,無論是串行隊列仍是併發隊列。

dispatch_sync(須要執行的線程名稱, ^{ 
      NSLog(@"同步線程");
});
複製代碼

而異步函數就不會等待任務的執行完成,它會當即返回。因此異步也就意味着會開啓一個新的線程,因此並不會阻塞當前的線程。

dispatch_async(須要執行的線程名稱, ^{ 
  NSLog(@"異步線程");
});
複製代碼

3. 隊列組

首先咱們看一下隊列組的基礎使用,假設有三個任務,第一個和第二個任務完成以後執行第三個任務:

// 獲取全局併發隊列
dispatch_queue_t currentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 獲取主線程
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 建立隊列組
dispatch_group_t groupQueue = dispatch_group_create();
// 將第一個任務加入隊列組
dispatch_group_async(groupQueue, currentGlobalQueue, ^{
    NSLog(@"並行任務1");
});
// 將第二個任務加入隊列組
dispatch_group_async(groupQueue, currentGlobalQueue, ^{
    NSLog(@"並行任務2");
});
// 將須要最後執行的任務加入隊列組
dispatch_group_notify(groupQueue, mainQueue, ^{
    NSLog(@"groupQueue中的任務 都執行完成,回到主線程更新UI");
});
複製代碼

隊列組部分API釋義以下 建立隊列組:

dispatch_group_create();
複製代碼

將任務加入隊列組:

dispatch_group_enter(dispatch_group_t  _Nonnull group);
複製代碼

參與爲須要加入的隊列組

標識加入隊列組的任務已經完成:

dispatch_group_leave(dispatch_group_t  _Nonnull group);
複製代碼

須要注意的是,enter和leave必須配對。

隊列裏邊全部任務執行完畢以後:

dispatch_group_notify(dispatch_group_t  _Nonnull group, dispatch_queue_t  _Nonnull queue, ^{
        NSLog(@"隊列裏邊全部的任務都執行完畢以後須要執行的事件寫到這");
});
複製代碼

3. 其餘dispatch方法

  • 阻塞當前線程dispatch_group_wait: dispatch_group_wait ,它會阻塞當前線程,直到組裏面全部的任務都完成或者等到某個超時發生。
dispatch_group_wait(dispatch_group_t  _Nonnull group, dispatch_time_t timeout);
複製代碼

示例代碼:

dispatch_group_t groupQueue = dispatch_group_create();
dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"開始異步任務");
dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
      NSLog(@"等待三秒");
dispatch_group_wait(groupQueue, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
      NSLog(@"執行並行任務");
});
複製代碼
  • dispatch_after延時添加到隊列 dispatch_after函數並不會在指定的時間後當即執行任務,時間到後,它只將任務加入到隊列上。所以這段代碼的做用和你在3秒鐘後用dispatch_async函數將block加到主線程上面的做用是同樣的。主線程隊列是在RunLoop上執行的,所以,假如RunLoop每1/60秒執行一次任務,那麼上面添加的block會在3秒~3+1/60秒後被執行。若是主線程隊列上加了不少任務,或者主線程延遲了,時間可能更晚。因此,將dispatch_after做爲一個精確的定時器使用是有問題的。若是你只是想粗略的延遲一下任務,這個函數仍是頗有用的。 函數的第二個參數指定了一個dispatch隊列,用於添加任務。第三個參數,是一個block,即要執行的任務。第一參數指定了延遲的時間,是一個dispatch_time_t類型的參數。這個值能夠用dispatch_time函數或dispatch_walltime函數來建立。 dispatch_time的第一個參數是指定的起始時間,第二個參數是以納秒爲單位的一個時間間隔。這個函數以起始時間和時間間隔來建立一個新的時間。如例中所示,一般以DISPATCH_TIME_NOW來做爲第一個參數的值,它表明了當前的時間。在下面這段代碼中,你能夠獲得一個dispatch_time_t類型的表示1秒鐘以後的時間的變量。 第二個參數中,NSEC_PER_SEC和數字的乘積會獲得一個以納秒爲單位的時間間隔值。若是使用NSEC_PER_MSEC,就會獲得一個以毫米爲單位的時間間隔值。
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(@"準備添加執行任務");
dispatch_after(delayTime3, mainQueue, ^{
      NSLog(@"3秒以後添加到隊列");
});
dispatch_after(delayTime2, mainQueue, ^{
      NSLog(@"2秒以後添加到隊列");
});
複製代碼
  • dispatch_apply屢次執行 dispatch_apply是同步執行的函數,不會馬上返回,在執行完block中的任務後纔會返回。功能是把一項任務提交到隊列中屢次執行,隊列能夠是串行也能夠是並行。 爲了避免阻塞主線程,dispatch_apply正確使用方法是把dispatch_apply放在異步隊列中調用,而後執行完成後通知主線程
dispatch_apply(size_t iterations, dispatch_queue_t  _Nonnull queue, ^(size_t)block)
複製代碼
  • 第一個參數是須要執行的次數
  • 第二個參數是準備提交的隊列
  • 第三個參數是block回調,裏邊編寫須要被重複執行的代碼

示例代碼:

dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
NSLog(@"準備添加執行任務");
dispatch_async(globalQueue, ^{
      dispatch_queue_t applyQueue = dispatch_get_global_queue(0, 0);
      dispatch_apply(3, applyQueue, ^(size_t currentCount) {
            NSLog(@"%tu",currentCount);
      });
      NSLog(@"dispatch_apply 執行完成");
});
複製代碼
  • dispatch_once 執行一次

    在app運行期間只執行block中的代碼只執行一次,經常使用於單例。 示例代碼:

    for (int i = 0; i < 10; i ++) {
        NSLog(@"%tu",i);
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSLog(@"執行一次");
        });
        sleep(1);
    }
    複製代碼
  • ** dispatch_barrier_async 柵欄**

    在並行隊列中添加多個任務,首先執行dispatch_barrier_async以前添加到隊列裏邊的任務,以後執行dispatch_barrier_async裏的任務,以後再執行dispatch_barrier_async以後添加到隊列裏的任務。必須注意它只對dispatch_queue_create(label, attr)接口建立的併發隊列有做用,若是是Global Queue(dispatch_get_global_queue),這個barrier就不起做用。

示例代碼:

dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.multithread.tempApp", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"第一個添加到隊列任務");
    });
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"第二個添加到隊列的任務");
    });
    dispatch_barrier_async(conCurrentQueue, ^{
        NSLog(@"dispatch barrier 阻塞任務");
    });
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"第三個添加到隊列的任務,在barrier以後");
    });
    dispatch_async(conCurrentQueue, ^{
        NSLog(@"最後一個添加到隊列的任務,在barrier以後");
    });
複製代碼
  • dispatch_set_target_queue 設置優先級
dispatch_set_target_queue(dispatch_object_t  _Nonnull object, dispatch_queue_t  _Nullable queue)
複製代碼

dispatch_set_target_queue函數用於設置一個」目標」隊列。這個函數主要用來爲新建立的隊列設置優先級。當用dispatch_queue_create函數建立一個隊列後,不管建立的是並行隊列仍是串行隊列,隊列的優先級都和全局dispatch隊列的默認優先級同樣。建立隊列後,你能夠用這個函數來修改隊列的優先級。下面的代碼演示瞭如何給一個串行隊列設置background優先級。

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.multithread.tempApp", NULL);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);
複製代碼

在上面代碼中,dispatch_set_target_queue函數的第一個參數是要設置優先級的隊列,第二個參數是一個全局的dispatch隊列,它會被做爲目標隊列。這段代碼會使隊列擁有和目標隊列同樣的優先級(稍後會解釋這裏的機制)。若是你將主線程隊列或者全局隊列傳遞給dispatch_set_target_queue函數的第一參數,結果不肯定,最後不要這麼幹。使用dispatch_set_target_queue函數不只可以設置優先級,也能建立隊列的層次體系。如圖7-8所示,一個串行隊列被設置成了多個串行隊列的目標隊列,在目標隊列上,一次只會有一個隊列被執行。

  • dispatch_semaphore_signal 信號量
// dispatch_semaphore_signal
    NSString *urlString = [@"https://www.baidu.com" stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
    // 設置緩存策略爲每次都從網絡加載 超時時間30秒
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:30];
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // 處理完成以後,發送信號量
        NSLog(@"正在處理...");
        dispatch_semaphore_signal(semaphore);
    }] resume];
    // 等待網絡處理完成
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"處理完成!");
複製代碼

須要注意的是:在上面的舉例中dispatch_semaphore_signal的調用必須是在另外一個線程調用,由於當前線程已經dispatch_semaphore_wait阻塞。另外,dispatch_semaphore_wait最好不要在主線程調用

  • dispatch_suspend(暫停)和dispatch_resume(繼續) 咱們可使用dispatch_suspend函數暫停一個queue以阻止它執行block對象;使用dispatch_resume函數繼續dispatch queue。調用dispatch_suspend會增長queue的引用計數,調用dispatch_resume則減小queue的引用計數。當引用計數大於0時,queue就保持掛起狀態。所以你必須對應地調用suspend和resume函數。掛起和繼續是異步的,並且只在執行block之間(好比在執行一個新的block以前或以後)生效。掛起一個queue不會致使正在執行的block中止

五. GCD的一些坑

經常使用的API和用法都說過了,接下來講一下GCD裏邊常見的坑。

  1. 在主隊列的主線程裏邊調用主線程執行同步任務,會產生死鎖:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue,^{
    NSLog(@"MainQueue");            
});
複製代碼

執行這段代碼會發現程序崩潰,block裏邊的MainQueue不會打印出來。 反之用異步API去執行這段代碼就沒問題

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue,^{
  NSLog("MainQueue");            
});
複製代碼

程序正常運行,block中的代碼正常運行 這段代碼崩潰的緣由是:主線程的獲取和執行都是在主隊列的主線程裏邊執行的,而後碰到了須要同步執行的代碼,主線程就會阻塞到同步代碼的地方,等待同步代碼執行完畢以後繼續執行。但是因爲FIFO協議的存在,串行隊列先進先出,因爲主隊列主線程的事件是先進去的,因此同步代碼會等待主隊列主線程代碼執行完畢纔會繼續執行。這樣就產生了死鎖,程序崩潰。 若是將這段代碼放到其餘異步隊列而後由主線程執行就不會崩潰了,代碼以下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_queue_t mainQueue = dispatch_get_main_queue();
        dispatch_sync(mainQueue,^{
            NSLog(@"MainQueue");
        });
    });
複製代碼

固然必須是異步隊列,若是是同步隊列的話仍是會產生崩潰,緣由和剛剛同樣。

  1. 在串行隊列的同步任務裏邊再執行一個同步任務,會發生死鎖:
dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serial_queue, ^{
        NSLog(@"串行1--同步1");
        dispatch_sync(serial_queue, ^{
            NSLog(@"串行1--同步2");
        });
    });
複製代碼

程序崩潰,NSLog(@"串行1--同步1");會被執行,NSLog(@"串行1--同步2");不會被執行。崩潰緣由和第一條是相同的,嚴格來講第一條只是第二條的一種特殊狀況。

  1. 在串行隊列的異步任務中再嵌套執行同步任務,也會發生死鎖:
dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serial_queue, ^{
        NSLog(@"串行1--異步1");
        dispatch_sync(serial_queue, ^{
            NSLog(@"串行1----同步1");
        });
        [NSThread sleepForTimeInterval:2.0];
    });
複製代碼

NSLog(@"串行1--異步1");會執行,NSLog(@"串行1----同步1");不會執行。一樣的,因爲串行隊列一次只能執行一個任務,任務結束後,才能執行下一個任務。因此異步任務的結束須要等裏面同步任務結束,而裏面同步任務的開始須要等外面異步任務結束,因此就相互等待,發生死鎖了。 3. dispatch_apply致使的死鎖

dispatch_queue_t queue = dispatch_queue_create("com.multithread.tempApp", DISPATCH_QUEUE_SERIAL);
dispatch_apply(3, queue, ^(size_t fitstApplyCount) {
    NSLog(@"first apply loop count: %tu", fitstApplyCount);
    //再來一個dispatch_apply!死鎖!
    dispatch_apply(3, queue, ^(size_t secondApplyCount) {
        NSLog(@"second apply loop count %tu", secondApplyCount);
    });
});
複製代碼

一些須要知道的知識:

  1. 主線程串行隊列由系統默認生成的,因此沒法調用dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。
  2. 全局併發隊列由系統默認生成的,因此沒法調用dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。

關於GCD的知識先寫到這,其實還有不少東西沒寫,若是有時間再續寫第二篇,不過在這能夠想一下下一篇都會涉及到什麼內容:

  1. GCD中不爲人所知可是頗有用的API
  2. GCD的線程安全
  3. 經常使用API實現原理與源碼解析

以上,就是GCD的調研結果,並無太底層的東西,大部分都是應用層面的,但願之後有時間把缺失的東西補上。





有志者、事竟成,破釜沉舟,百二秦關終屬楚;

苦心人、天不負,臥薪嚐膽,三千越甲可吞吳.

相關文章
相關標籤/搜索