GCD使用匯總

dispatch_queue、dispatch_block

dispatch_block_t其實是一個自定義block類型node

typedef void (^dispatch_block_t)(void);

蘋果對於它的註釋至關有意思:
編程


若是不使用ARC,分配在堆上或者複製到堆上的block對象必定要經過發送release消息或者調用Block_release函數來釋放。
block字面量的聲明是在棧空間上,必定要杜絕這樣的建立:

dispatch_block_t block;
 if (x) {
     block = ^{ printf("true\n"); };
 } else {
     block = ^{ printf("false\n"); };
 }
 block(); // unsafe!!!

這樣其實至關於swift

if (x) {
     struct Block __tmp_1 = ...; // setup details
     block = &__tmp_1;
 } else {
     struct Block __tmp_2 = ...; // setup details
     block = &__tmp_2;
 }
這個示範中,棧變量的地址脫離了它聲明時的做用域。這是一個經典的C語言bug。 取而代之的應該是:block字面量必須經過Block_copy函數或者發送copy消息複製到堆上。

dispatch_queue_t是任務執行的隊列類型,它經過dispatch_queue_create建立,有兩種類型

DISPATCH_QUEUE_SERIAL(NULL): 串行隊列

DISPATCH_QUEUE_CONCURRENT: 並行隊列數組

串行隊列中的任務是有序執行的,並行隊列中的任務是無序執行的,具體還要看以同步方式執行仍是以異步方式執行。緩存

兩個特殊的隊列:
main quue: dispatch_get_main_queue()獲取。
global queue: dispatch_get_global_queue(0, 0)獲取,這個函數的第一個參數是服務質量;第二個參數爲保留值,始終傳0.這個隊列又叫作全局並行隊列。
這兩個隊列的特色是:
放在主隊列中的任務必定在主線程中執行。
放在全局隊列中的任務必定是在子線程中執行。(大部分狀況是對的,可是dispatch_sync方法優化爲:使用當前線程)安全

dispatch_sync dispatch_async

這部分經過官方文檔和一些任務執行順序分析來理解這兩個方法的區別,順序分析時要注意如下幾個因素:網絡

1.環境:當前所處的線程

2.隊列:將任務添加到了哪一種類型的隊列

3.執行方式:同步仍是異步
多線程

①dispatch_sync

這個方法阻塞,也就是說任務必定是添加到隊列中以後而且執行完成以後,程序纔會繼續運行。
蘋果的文檔說的很清楚:Submits a block object for execution on a dispatch queue and waits until that block completes.
Submits a block to a dispatch queue for synchronous execution. Unlike dispatch_async, this function does not return until the block has finished. Calling this function and targeting the current queue results in deadlock.。所以下面的程序不管換成什麼隊列執行結果相同:併發

如下環境都是在主線程中app

dispatch_queue_t serialQueue = dispatch_queue_create("com.mikezh.serial.test", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.mikezh.concurrent.test", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

NSLog(@"begin");
for (NSUInteger i = 0; i < 10; i++) {
    dispatch_sync(concurrentQueue/*serialQueue globalQueue 都是同樣的*/, ^{
        if (i == 2) {
            sleep(2);
        }
        if (i == 5) {
            sleep(5);
        }
        NSLog(@"任務%zd, %@", i, [NSThread currentThread]);
    });
}
NSLog(@"end");
begin
任務0, <NSThread: 0x600000077f80>{number = 1, name = main}
任務1, <NSThread: 0x600000077f80>{number = 1, name = main}
任務2, <NSThread: 0x600000077f80>{number = 1, name = main}
任務3, <NSThread: 0x600000077f80>{number = 1, name = main}
任務4, <NSThread: 0x600000077f80>{number = 1, name = main}
任務5, <NSThread: 0x600000077f80>{number = 1, name = main}
任務6, <NSThread: 0x600000077f80>{number = 1, name = main}
任務7, <NSThread: 0x600000077f80>{number = 1, name = main}
任務8, <NSThread: 0x600000077f80>{number = 1, name = main}
任務9, <NSThread: 0x600000077f80>{number = 1, name = main}
end

這裏global queue執行代碼調度的線程取決於環境(可見global queue中任務必定是在子線程執行這個說法是錯誤的,這個在於dispatch_sync方法的優化:As an optimization, this function invokes the block on the current thread when possible.),例如:

dispatch_async(globalQueue, ^{
    NSLog(@"begin");
    for (NSUInteger i = 0; i < 10; i++) {
        dispatch_sync(globalQueue, ^{
            if (i == 2) {
                sleep(2);
            }
            if (i == 5) {
                sleep(5);
            }
            NSLog(@"任務%zd, %@", i, [NSThread currentThread]);
        });
    }
    NSLog(@"end");
});
begin
任務0, <NSThread: 0x61800006f880>{number = 3, name = (null)}
任務1, <NSThread: 0x61800006f880>{number = 3, name = (null)}
任務2, <NSThread: 0x61800006f880>{number = 3, name = (null)}
任務3, <NSThread: 0x61800006f880>{number = 3, name = (null)}
任務4, <NSThread: 0x61800006f880>{number = 3, name = (null)}
任務5, <NSThread: 0x61800006f880>{number = 3, name = (null)}
任務6, <NSThread: 0x61800006f880>{number = 3, name = (null)}
任務7, <NSThread: 0x61800006f880>{number = 3, name = (null)}
任務8, <NSThread: 0x61800006f880>{number = 3, name = (null)}
任務9, <NSThread: 0x61800006f880>{number = 3, name = (null)}
end

另外,蘋果也說明了什麼狀況下會形成死鎖:在currentqueue中調用dispatch_sync方法,而且將任務添加到currentqueue中,也就是說下面的代碼會形成死鎖:

// 主線程、主隊列、同步執行=====>死鎖
dispatch_sync(mainQueue, ^{
});
dispatch_async(serialQueue, ^{
    dispatch_sync(serialQueue, ^{
    });
});

③dispatch_async

向queue提交異步執行的block並當即返回。
這個函數是向隊列提交block的基本機制。
調用這個函數老是在提交block以後當即返回而且從不等待block的調用。
目標queue會參照其餘的block來決定block是串行仍是並行執行。相互獨立的串行隊列參照別的串行隊列來並行處理。
參數:queue
block提交到的queue.這個queue會被系統持有直到block運行完畢。
block
提交到目標調度queue中的block。這個函數會幫你執行Block_copy和Block_release。

dispatch_async能夠用來分析任務執行時要考慮的就是:block提交的順序,block開始執行的順序。
1.環境中後面的代碼不會等待block的執行
2.對於串行隊列而言,block執行的順序只能和執行的順序相同,
對於並行隊列而言,由於任務的執行是並行的,因此產生提交的block順序和執行的順序不一致的狀況。

對於如下程序:

NSLog(@"begin");
for (NSUInteger i = 0; i < 10; i++) {
    dispatch_async(queue, ^{
        if (i == 2) {
            sleep(2);
        }
        if (i == 5) {
            sleep(5);
        }
        NSLog(@"任務%zd, %@", i, [NSThread currentThread]);
    });
}
NSLog(@"end");

若是queue是mainQueue

begin
end
任務0, <NSThread: 0x60000006de80>{number = 1, name = main}
任務1, <NSThread: 0x60000006de80>{number = 1, name = main}
任務2, <NSThread: 0x60000006de80>{number = 1, name = main}
任務3, <NSThread: 0x60000006de80>{number = 1, name = main}
任務4, <NSThread: 0x60000006de80>{number = 1, name = main}
任務5, <NSThread: 0x60000006de80>{number = 1, name = main}
任務6, <NSThread: 0x60000006de80>{number = 1, name = main}
任務7, <NSThread: 0x60000006de80>{number = 1, name = main}
任務8, <NSThread: 0x60000006de80>{number = 1, name = main}
任務9, <NSThread: 0x60000006de80>{number = 1, name = main}

若是是serialQueue

begin
end
任務0, <NSThread: 0x61000026a000>{number = 3, name = (null)}
任務1, <NSThread: 0x61000026a000>{number = 3, name = (null)}
任務2, <NSThread: 0x61000026a000>{number = 3, name = (null)}
任務3, <NSThread: 0x61000026a000>{number = 3, name = (null)}
任務4, <NSThread: 0x61000026a000>{number = 3, name = (null)}
任務5, <NSThread: 0x61000026a000>{number = 3, name = (null)}
任務6, <NSThread: 0x61000026a000>{number = 3, name = (null)}
任務7, <NSThread: 0x61000026a000>{number = 3, name = (null)}
任務8, <NSThread: 0x61000026a000>{number = 3, name = (null)}
任務9, <NSThread: 0x61000026a000>{number = 3, name = (null)}

若是是globalQueue

begin
end
任務0, <NSThread: 0x60800007d6c0>{number = 3, name = (null)}
任務1, <NSThread: 0x61000007ca80>{number = 4, name = (null)}
任務4, <NSThread: 0x618000261040>{number = 6, name = (null)}
任務3, <NSThread: 0x60000007f780>{number = 5, name = (null)}
任務6, <NSThread: 0x60000007e0c0>{number = 7, name = (null)}
任務7, <NSThread: 0x61000007bfc0>{number = 8, name = (null)}
任務8, <NSThread: 0x60800007d6c0>{number = 3, name = (null)}
任務9, <NSThread: 0x60800007d540>{number = 9, name = (null)}
任務2, <NSThread: 0x60800007d8c0>{number = 10, name = (null)}
任務5, <NSThread: 0x600000262b00>{number = 11, name = (null)}

若是是concurrentQueue

begin
end
任務1, <NSThread: 0x610000073a40>{number = 4, name = (null)}
任務0, <NSThread: 0x618000065040>{number = 3, name = (null)}
任務3, <NSThread: 0x600000068f80>{number = 5, name = (null)}
任務4, <NSThread: 0x618000067bc0>{number = 6, name = (null)}
任務6, <NSThread: 0x60800006ec80>{number = 7, name = (null)}
任務7, <NSThread: 0x6180000679c0>{number = 8, name = (null)}
任務8, <NSThread: 0x61000006cdc0>{number = 9, name = (null)}
任務9, <NSThread: 0x610000073a40>{number = 4, name = (null)}
任務2, <NSThread: 0x610000073c40>{number = 10, name = (null)}
任務5, <NSThread: 0x618000064d40>{number = 11, name = (null)}

dispatch_set_target_queue、dispatch_object_t

dispatch_set_target_queue的做用是:爲指定的object設置目標隊列。
目標隊列負責處理這個object。object最後執行所在的隊列取決於目標隊列。另外,修改目標隊列會影響一些object的行爲:

object爲Dispatch queues:

這個object,也就是這個queue會繼承目標隊列的優先級。可使用dispatch_get_global_queue函數獲取一個有期待的優先級的合適的目標隊列。
若是你向串行隊列提交block,同時這個串行隊列的目標隊列是一個不一樣的串行隊列,這個block相對於已經提交到目標隊列中的其餘block不會異步執行,對於設置一樣目標隊列的其餘隊列中的block也不會異步執行。

Important

若是你爲一個隊列修改了目標隊列,你必須當心以免建立隊列層級的循環(目標環)。

object爲Dispatch sources:

目標隊列爲source指定了它的事件處理和取消處理的block將會提交到哪裏。

object爲Dispatch I/O channels:

目標隊列爲I/O channel指定了I/O操做將在哪裏執行。這會影響到I/O操做的優先級。好比,若是channel的目標隊列優先級設置爲DISPATCH_QUEUE_PRIORITY_BACKGROUND,dispatch_io_read和dispatch_io_write函數進行的任何I/O操做都會在發生資源競爭時中止。

self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(self.timer, ^{
    NSLog(@"hahah--%@", [NSThread currentThread]);
});
dispatch_set_target_queue(self.timer, mainQueue); // timer在主線程上執行
dispatch_resume(self.timer);

dispatch_set_target_queue方法的第一個參數object是dispatch_object類型卻能夠傳遞多種類型,這是爲何呢?

dispatch_source_t是如何定義的

// source.h
DISPATCH_SOURCE_DECL(dispatch_source);

// object.h // 非swift環境下
DISPATCH_DECL(name);

// object.h 非swift環境下
#define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS(name, dispatch_object)

// object.h 非swift環境下
#define OS_OBJECT_DECL_SUBCLASS(name, super) \
        OS_OBJECT_DECL_IMPL(name, <OS_OBJECT_CLASS(super)>)

// 下面的①②③是對這個宏的展開
#define OS_OBJECT_DECL_IMPL(name, ...) \
        OS_OBJECT_DECL_PROTOCOL(name, __VA_ARGS__) \
        typedef NSObject<OS_OBJECT_CLASS(name)> \
                * OS_OBJC_INDEPENDENT_CLASS name##_t
// ①
#define OS_OBJECT_DECL_PROTOCOL(name, ...) \
        @protocol OS_OBJECT_CLASS(name) __VA_ARGS__ \
        @end
// ②
#define OS_OBJECT_CLASS(name) OS_##name
// ③
#if __has_attribute(objc_independent_class)
#define OS_OBJC_INDEPENDENT_CLASS __attribute__((objc_independent_class))
#endif // __has_attribute(objc_independent_class)
#ifndef OS_OBJC_INDEPENDENT_CLASS
#define OS_OBJC_INDEPENDENT_CLASS
#endif

所以dispatch_source_t徹底展開就是:

@protocol OS_dispatch_source <OS_dispatch_object>
@end
typedef NSObject<OS_dispatch_source>* dispatch_source_t

dispatch_io_t是如何定義的

DISPATCH_DECL(dispatch_io);
OS_OBJECT_DECL_SUBCLASS(name, dispatch_object)
// ...

所以dispatch_source_t徹底展開就是:

@protocol OS_dispatch_io <OS_dispatch_object>
@end
typedef NSObject<OS_dispatch_io>* dispatch_io_t

dispatch_queue_t就是

@protocol OS_dispatch_queue <OS_dispatch_object>
@end
typedef NSObject<OS_dispatch_queue>* dispatch_queue_t

相似的還有dispatch_semaphore、dispatch_data_t、dispatch_group_t這幾個類型。
他們都是一個遵照相應協議的NSObject對象類型,這些協議的基協議OS_dispatch_object就是由dispatch_object_t聲明的:

OS_OBJECT_DECL_CLASS(dispatch_object);
#define OS_OBJECT_DECL_CLASS(name) \
        OS_OBJECT_DECL(name)
#define OS_OBJECT_DECL(name, ...) \
        OS_OBJECT_DECL_IMPL(name, <NSObject>)
#define OS_OBJECT_DECL_IMPL(name, ...) \
        OS_OBJECT_DECL_PROTOCOL(name, __VA_ARGS__) \
        typedef NSObject<OS_OBJECT_CLASS(name)> \
                * OS_OBJC_INDEPENDENT_CLASS name##_t

徹底展開就是:

@protocol OS_dispatch_object <NSObject>
@end
typedef NSObject<OS_dispatch_object> * dispatch_object_t

dispatch_after dispatch_time_t

dispatch_after函數會在指定的時刻將block異步地添加到指定的隊列。
支持傳遞DISPATCH_TIME_NOW做爲when參數,可是不如調用dispatch_async更優。不能夠傳遞DISPATCH_TIME_FOREVER。

要注意的是:

並非在指定的時間後執行處理,而是在指定時間追加block到隊列中,由於mainQueue在主線程的runloop中執行,因此在好比每隔1/60秒執行的RunLoop中。block最快在指定時刻執行,最慢在指定時刻+1/60秒執行,而且在main queue中有大量處理追加或主線程的處理自己有延遲時,這個時間會更長。

這個方法的第一個參數是dispatch_time_t類型,它其實是:

typedef uint64_t dispatch_time_t;

它是對時間的一個抽象表示,0表明如今, DISPATCH_TIME_FOREVER表明着無限大,能夠經過兩個函數建立

/// 根據默認時鐘建立一個時間,或者修改一個已存在的時間
/// OS X 默認時鐘基於mach_absolute_time()函數
/// 參數when:須要修改的時間,若是傳遞0,這個函數會使用mach_absolute_time()返回值。
/// 參數delta:要添加的納秒數
dispatch_time_t
dispatch_time(dispatch_time_t when, int64_t delta);
/// 使用系統時間建立一個時間類型值
/// OS X 系統時鐘基於gettimeofday(3)函數
/// 參數when:須要修改的時間或依據時間,是一個struct timespec類型指針,若是傳遞NULL,這個函數會使用gettimeofday(3)返回值。
/// 參數delta:要添加的納秒數
dispatch_time_t
dispatch_walltime(const struct timespec *_Nullable when, int64_t delta);

dispatch_group

group對於多個任務結束後執行一些操做很是有用。Apple這樣介紹:
一組block對象提交到一個隊列中來異步調用。group是一個管理一系列block的機制。你的程序能夠根據須要同步或者異步監控這些block。另外,group對於一些依賴其餘操做完成的同步代碼很是有用。
要注意的是:group中的block能夠在不一樣的queue中執行,每個block中能夠添加更多block到group中
group會追蹤有多少個未完成的block,GCD會持有group,直到全部相關的block所有執行完成。
舉個例子:下載A、B、C三個文件,所有下載完成以後提示

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, globalQueue, ^{
    NSLog(@"downloading A ...");
});
dispatch_group_async(group, globalQueue, ^{
    NSLog(@"downloading B ...");
});
dispatch_group_async(group, globalQueue, ^{
    NSLog(@"downloading C ...");
});
dispatch_group_notify(group, mainQueue, ^{
    NSLog(@"下載完成");
});

若是是想等待所有執行完成以後再進行其餘的代碼可使用

dispatch_group_wait(group, time)

注意這個方法是阻塞方法,它有個特色:會一直等待直到到達等待的時間 或 任務執行完成才返回。
對於它的返回值,若是返回值爲0,表明group中的任務已經所有完成,非0則沒有完成。

但實際開發中有這樣的場景,好比進行一組網絡請求任務,每個任務都是異步任務,而請求完成並將數據解析完畢咱們才認爲是任務的完成,而這些過程又有多是跨線程的。這時候就要使用dispatch_group_enter和dispatch_group_leave組合,經過它們能夠對group進行更細粒度的控制。這兩個函數都是線程安全的,對應着添加和移除任務,所以使用時必需要成對出現。

dispatch_group_enter(group);
dispatch_group_async(group, globalQueue, ^{
    dispatch_async(globalQueue, ^{
        NSLog(@"downloading A ...");
        dispatch_group_leave(group);
    });
});

dispatch_group_async(group, globalQueue, ^{
    dispatch_group_enter(group);
    dispatch_async(globalQueue, ^{
        NSLog(@"downloading B ...");
        dispatch_group_leave(group);
    });
});
dispatch_group_enter(group);
dispatch_group_async(group, globalQueue, ^{
    dispatch_async(globalQueue, ^{
        NSLog(@"downloading C ...");
        dispatch_group_leave(group);
    });
});
dispatch_group_notify(group, mainQueue, ^{
    NSLog(@"下載完成");
});

dispatch_barrier

咱們知道在對於並行隊列,使用async方法執行任務,任務被添加到隊列中是有序的,可是執行無序。但有這麼一個場景:對於數據讀操做併發執行沒有問題,可對於寫操做來講卻要控制寫過程當中再也不進行讀操做,以免數據競爭問題。相似的場景不少,大致歸結爲在許多併發任務中,有1個任務在執行的時候必須保證其餘的任務等待其執行完畢.諸如此類的問題可使用dispatch_barrier_asyn函數解決。

dispatch_barrier_async

提交一個異步執行的barrier block並當即返回。
調用這個函數老是在block被提交以後當即返回,而從不等待block的執行。當barrier block到達自定義併發隊列的隊頭時,它不會被當即執行。它會等待直到當前正在執行的lock執行完畢,到這時,barrier block纔會執行。
任何在barrier block後套面提交的block也不會執行,直到barrier block執行完畢。
你指定的隊列應當是一個使用dispatch_queue_create函數本身建立的並行隊列。若是傳給這個函數一個串行隊列或
global並行隊列,這個函數會像dispatch_async函數同樣。

下圖能夠很好的說明這個函數的做用

dispatch_async(concurrentQueue, ^{
    NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"2");
});
dispatch_barrier_async(concurrentQueue, ^{
    sleep(5);
    NSLog(@"barrier");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"3");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"4");
});

能夠測試打印結果:

2
1
barrier
3
4
// 一、2無序必定在以前, 三、4無序必定在以後

dispatch_barrier_sync

提交一個barrier block並等待這個block執行完畢。
提交一個barrier block到隊列用來同步執行。不一樣於dispatch_barrier_async,這個函數直到barrier block執行完畢才返回。目標隊列是當前隊列會發生死鎖。當barrier block到達自定義併發隊列的隊頭時,它不會被當即執行。它會等待直到當前正在執行的lock執行完畢,到這時,barrier block纔會執行。任何在barrier block後套面提交的block也不會執行,直到barrier block執行完畢。
你指定的隊列應當是一個使用dispatch_queue_create函數本身建立的並行隊列。若是傳給這個函數一個串行隊列或
global並行隊列,這個函數會像dispatch_sync函數同樣。
不一樣於dispatch_barrier_async,系統不會持有目標隊列。由於調用這個函數是同步的,它借用了調用着的引用。並且也不會對block進行Block_copy操做。
做爲優化,這個函數儘量在當前線程調用barrier block

能夠看到:最後面的優化說明和dispatch_sync方法徹底一致。
這個函數和dispatch_barrier_async的區別就在於可否阻塞當前的線程。
測試:

dispatch_async(concurrentQueue, ^{
    NSLog(@"1");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"2");
});
// 阻塞
dispatch_barrier_sync(concurrentQueue, ^{
    sleep(5); // 若是當前環境爲主線程,則界面frozen
    NSLog(@"barrier sync執行");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"3");
});
dispatch_async(concurrentQueue, ^{
    NSLog(@"4");
});

dispatch_apply

爲執行多個操做向隊列提交一個block,而且在返回以前等待全部的操做完成。若是目標隊列是dispatch_get_global_queue返回的並行隊列,block會被並行執行,所以它必須是可重入安全的。配合並行隊列使用這個方法對於一個循環的高效併發來講很是有用。

NSLog(@"begin");
dispatch_apply(5, globalQueue, ^(size_t index) {
    NSLog(@"%zd", index);
});
NSLog(@"end");

結果爲:

begin
0
1
2
4
3
end

能夠利用這個方法高效地處理數組中的數據,不過要注意:雖然會等待全部的任務執行完成才返回,但每一個任務的執行是異步無序的。

dispatch_apply(array.count, globalQueue, ^(size_t index) {
    id element = array[index];
    // handler
});

dispatch_suspend、dispatch_resume

dispatch_suspend

暫停在dispatch object上的block的執行。

經過暫停一個dispatch object,你的程序能夠暫時阻止與這個object有關的任何block的執行。這個暫停發生在調用方法時全部正在執行的block完成以後。調用這個函數會遞增object的暫停數,而調用dispatch_resume會少這個計數,因此你必須用一個相匹配的dispatch_resume調用來平衡每一次的dispatch_suspend調用。

一旦object被恢復,任何提交到隊列中的block或者經過dispatch source觀察的事件就會執行。

dispatch_suspend(queue)

dispatch_suspend(timer)

dispatch_resume

繼續執行dispatch object上的block。

調用這個方法會遞減暫停的隊列數或暫停的事件源對象。當計數大於0時,對象會保持暫停。當暫停數重置爲0,任何提交到隊列中的block或者經過dispatch source觀察的事件會被執行。

有一個例外:每次調用dispatch_resume必須是來平衡調用dispatch_suspend的。新的經過dispatch_source_create函數返回的事件源對象有一個值爲1暫停計數,所以必須在事件執行以前resume。這個方法使你的程序在事件分發以前完整地配置事件源對象。對於其餘狀況,都不容許比調用dispatch_suspend的次數多,那會致使負的暫停計數。

dispatch_resume(queue)

dispatch_resume(timer)

dispatch_semaphore

semaphore即信號量。 在多道程序環境下,操做系統如何實現進程之間的同步和互斥顯得極爲重要。荷蘭學者Dijkstra給出了一種解決併發進程間互斥與同步關係的通用方法,即信號量機制。
信號量是一個具備非負初值的整型變量,而且有一個隊列與它關聯。信號量除初始化外,僅能經過P、V 兩個操做來訪問,這兩個操做都由原語組成,即在執行過程當中不可被中斷,也就是說,當一個進程在修改某個信號量時, 沒有其餘進程可同時對該信號量進行修改。P操做信號量減1,若是信號量≥0,表示能夠繼續執行;若是<0就要阻塞等待,直到信號量>=0。V操做信號量加1。

信號量能夠模擬現實中相似於通行證的概念,即信號量>=0能夠通行,而信號量<0時則須要等待增長才能夠以通行。所以信號量機制編程必涉及三個函數,建立信號量、增長信號量、減小信號量。

dispatch_semaphore_t 
dispatch_semaphore_create(long value)

建立一個信號量,參數爲初始值。
傳入0適用於兩個線程須要解決對於一個資源的競爭。
傳入一個大於0的值適用於管理一個有限的資源池,這個池子的大小與傳入的值相等。
當你的程序再也不須要信號量時,應該調用dispatch_release來釋放它對信號量對象的引用並最終釋放內存。(ARC會幫助處理,所以不要手動調用dispatch_release()函數)

long
dispatch_semaphopre_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

等待 (減小)一個信號量.
遞減信號量計數。若是遞減以後的結果值小於0,這個方法會在返回以前一直等待一個signal信號。

long 
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

signal或者說遞增一個信號量。
遞增信號量計數。若是以前的值小於0,這個函數會異步地喚起一個正在dispatch_semaphore_wait函數中等待的線程。

模擬一次資源競爭的問題,在多個線程中操做同一個數據是很是常見的資源競爭的狀況,這樣很是容易引發數據不一致,有時候應用會異常結束。咱們使用dispatch_barrier_async能夠解決這個問題,可是它是對整塊block任務的隔離,而並無細微到對要操做的數據這個粒度的限制。例如使用可變數組模擬多線程寫數據的狀況(只是模擬寫數據過程,不考慮順序),

NSUInteger count = 10;
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:count];
for (NSUInteger i = 0; i < count; i++) {
    dispatch_async(globalQueue, ^{
        [mutableArray addObject:[NSNumber numberWithInteger:i]];
    });
}

在多線程中更新NSMutableArray, 這段代碼異常率是極高的。可使用信號量機制進行保證線程安全性,任何一個正在寫的操做必需要完成以後,才能進行下一個寫的操做:

NSUInteger count = 10;
self.mutableArray = [NSMutableArray arrayWithCapacity:count];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (NSUInteger i = 0; i < count; i++) {
    dispatch_async(globalQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [self.mutableArray addObject:[NSNumber numberWithInt:i]]; // 執行到這裏說明沒有阻塞,即信號量依然>=0
        dispatch_semaphore_signal(semaphore);
    });
}

dispatch_once

void
dispatch_once(dispatch_once_t *predicate,
        DISPATCH_NOESCAPE dispatch_block_t block);

在程序的生命週期只執行block一次

這個函數對程序中的全局數據(單例)的初始化很是有用。老是在使用或測試任何經過block初始化的變量以前使用這個函數。

若是在多個線程中同時調用,這個函數會同步地等待知道block執行完成。

這個predicate參數必須指向一個保存在全局區或靜態區的變量。使用自動存儲或動態存儲變量(包括OC實例變量)的predicate結果是未知的。

static Singleton *instance;

+ (instancetype)shareInstance {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (!instance) {
            instance = [[[self class] alloc] init];
        }
    });
    return instance;
}

dispatch_io、dispatch_data

讀取較大文件時,若是將文件分紅合適的大小並使用Global Queue並行讀取的話應該會比通常的讀取速度快很多,現今的輸入輸出硬件已經能夠作到一次使用多個線程更快地並列讀取了,能實現這一功能的就是dispatch_io和dispatch_data.
使用dispatch_io讀寫文件能夠將1個文件固定大小分爲快分別在多個線程read/write.

dispatch_async (queue, ^{/*讀取     0 ~ 8191  字節*/});
dispatch_async (queue, ^{/*讀取  8192 ~ 16383 字節*/});
dispatch_async (queue, ^{/*讀取 16384 ~ 24575 字節*/});
dispatch_async (queue, ^{/*讀取 24576 ~ 36767 字節*/});

這裏有一個能夠分塊讀取,而後拼裝爲NSData的方法:

void read_file(int fd, void(^completion)(NSData *data)) {
    NSMutableData *data = [NSMutableData data];
    dispatch_queue_t pipe_q;
    dispatch_io_t pipe_channel;
    
    pipe_q = dispatch_queue_create("PipeQ", NULL);
//    pipe_q = dispatch_get_main_queue();
    
    pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
        close(fd);
    });
    
    dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
    
    dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
        NSLog(@"%@", [NSThread currentThread]);
        if (err == 0) {
            size_t len = dispatch_data_get_size(pipedata);
            if (len > 0) {
                const char *bytes = NULL;
                (void)dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
                [data appendBytes:bytes length:len];
            }
        }
        
        if (done && completion) {
            completion(data.copy);
        }
    });
}

使用時,傳入文件描述便可:

int fd = open("/Users/Mike/Desktop/a.txt", O_RDWR);
read_file(fd, ^(NSData *data) {
    NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
});

dispatch_source

GCD中除了主要的Dispatch Queue外,還有不太引人注目的Dispatch Source。它是BSD系統內核慣有功能kqueue的包裝。
kqueue是在XNU內核發生各類事件時,在應用程序編程方執行處理的技術。其CPU負荷很是小,儘可能不佔用資源,kqueue能夠說是應用程序處理XNU內核發生的各類事件的方法中最優秀的一種。

dispatch_source使用流程大體以下:

1.經過dispatch_source_create()函數建立一個source

2.經過dispatch_source_set_event_handler()函數爲source指定處理的block。

經過dispatch_source_set_cancel_handler()函數爲source指定取消的回調,這個回調會經過dispatch_source_cancel()函數的調用觸發。

3.經過dispatch_resume()函數啓動source

dispatch_source_create

建立一個新的source來管理底層的系統對象,同時自動提交handler block到GCD隊列中來響應事件。

GCD source不是可重入的。任何在source暫停時或者在handler block正在執行時接收到的事件都會合並而後在source恢復或者事件handler block返回以後分發。GCD source建立時的狀態是掛起的。在建立以後或者設置一些屬性(好比handler或者context)以後,你的程序必須調用dispatch_resume來開始這個事件的分發。

若是你的app沒有使用ARC,你應該在再也不使用source的時候調用dispatch_release來釋放它

Important

事件source的建立時異步的,因此要搞清楚被監控的系統句柄產生的競爭條件。好比,若是一個source是爲一個進程建立的,同時這個進程在source建立以前就存在了,那麼任何設定的取消處理都不會被調用。

參數

type

source的類型。必須是下面列出的source類型常量之一

type 內容
DISPATCH_SOURCE_TYPE_DATA_ADD 變量增長
DISPATCH_SOURCE_TYPE_DATA_OR 變量OR
DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口發送
DISPATCH_SOURCE_TYPE_MACH_SEND Mach端口接收
DISPATCH_SOURCE_TYPE_PROC 檢測到與進程相關的事件
DISPATCH_SOURCE_TYPE_READ 可讀取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL 接收信號
DISPATCH_SOURCE_TYPE_TIMER 定時器
DISPATCH_SOURCE_TYPE_VNODE 文件系統有變動
DISPATCH_SOURCE_TYPE_WRITE 可寫入文件映像
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 內存壓力



handle

要監控的底層系統句柄。這個參數的具體值依賴於type參數常量。

mask

指望的事件指定的flags的掩碼。這個參數的具體值依賴於type參數常量。

queue

事件處理的block提交到的GCD隊列。

Returns

建立成功返回新建立的source,不然返回NULL。

dispatch_source_set_event_handler

爲指定的source設置事件處理block。

事件處理(若是設置了)會提交到source的目標隊列來響應事件的發生。

dispatch_source_set_cancel_handler

爲指定的source設置事件取消block。
取消block(若是設置了)會提交到source的目標隊列來響應dispatch_source_cancel的調用,這個響應發生在系統釋放了全部對source的底層句柄,同時,source事件處理的block已經返回了

Important

爲了能安全地關閉文件描述或者銷燬Mach port, 取消的處理對文件描述或者port來講是必須的。在取消處理執行以前關閉描述和port會產生競爭條件。當source事件處理仍在運行時,若是有一個新的描述被分配了與最近關閉的描述一樣的值,事件處理可能會使用錯誤的描述讀寫數據。

dispatch_source_cancel

異步地取消source,阻止對事件處理block的再次調用。

取消操做阻止任何對事件處理block的再次調用,可是不會打斷正在執行的事件處理。一旦事件處理block執行完畢,這個可選的取消處理會提交到目標隊列。(事件處理至少執行一次??)

取消操做會在時間處理執行完成時提交到目標隊列,意味着關閉source的句柄(文件描述或者mach port)是安全的。

這個可選的取消處理只會在系統釋放了全部的對底層系統對象(文件描述或mach prots)的引用以後才提交到目標隊列。所以,取消處理是一個關閉和釋放這些系統對象很是方便的地方。要注意的是,若是文件描述或mach port最近被被source對象追蹤,在取消操做執行以前關閉或者釋放它們是無效的.

dispatch_source_set_timer

爲一個timer source設置開始時間,間隔,誤差。

你的程序能夠根據須要在一個timer source對象上屢次調用這個函數來重置時間間隔。

開始時間這個參數也決定了這個timer上將使用什麼時鐘。若是開始時間是DISPATCH_TIME_NOW或者是dispatch_time函數建立的,這個timer基於mach_absolute_time。不然,timer的開始時間是dispatch_walltime建立的,這個timer基於gettimeofday(3)。

誤差參數是程序時間值的微量,它以毫秒爲單位,爲了提高系統性能和解決耗電量,它能夠大到系統將timer延遲到與其餘系統活動結合使用。例如,一個每5分鐘執行一個週期性任務,而程序可能會使用一個最多30秒的leeway值。要注意的是:對全部的timer而言,即便leeway值指定爲0,一些潛在問題也是正常的。

調用這個函數不會對已經取消的timer source有做用。

參數

start

timer開始的時間。查看 dispatch_time 和 dispatch_walltime 獲取更多信息。

interval

以毫秒爲單位的時間間隔

leeway

系統能夠延遲這個timer的時間值,以毫秒爲單位。

dispatch_source_testcancel

測試指定的source是否已經被取消。

你的程序會使用這個函數來測試GCD source對象是否已經被經過調用dispatch_source_cancel的方式取消。若是dispatch_source_cancel已經調用過了,這個函數會馬上返回一個非0值,若是沒有被取消則返回0.

一些其餘方法

dispatch_source_get_data

返回數據。

要在事件處理的block中調用這個函數。在外面調用會發生意想不到的結果。

返回值(unsigned long )

根據source的type的不一樣會有不一樣的返回值,共有如下幾種:

type 返回值
DISPATCH_SOURCE_TYPE_DATA_ADD 程序定義的數據
DISPATCH_SOURCE_TYPE_DATA_OR 程序定義的數據
DISPATCH_SOURCE_TYPE_MACH_SEND Dispatch Source Mach Send Event Flags
DISPATCH_SOURCE_TYPE_MACH_RECV 不適用
DISPATCH_SOURCE_TYPE_PROC Dispatch Source Process Event Flags
DISPATCH_SOURCE_TYPE_READ 預估可讀字節數
DISPATCH_SOURCE_TYPE_SIGNAL 上次處理執行後的分發的signal數目
DISPATCH_SOURCE_TYPE_TIMER 上次處理執行以後,timer啓動後執行的次數
DISPATCH_SOURCE_TYPE_VNODE Dispatch Source Vnode Event Flags
Dispatch Source Memory Pressure Event Flags 可用的預估緩存空間
dispatch_source_get_mask

返回source監控的事件的掩碼。

這個掩碼是一個事件source監控的相關事件的位掩碼。任何在這個事件掩碼裏沒有指定的事件都胡被忽略,同時不會爲這些事件提交事件處理block。

更詳細的信息 查看flag描述常量。

返回值

返回值根據source的type不一樣,會是如下flag集合中的一種:

type 返回值
DISPATCH_SOURCE_TYPE_MACH_SEND Dispatch Source Mach Send Event Flags
DISPATCH_SOURCE_TYPE_PROC Dispatch Source Process Event Flags
DISPATCH_SOURCE_TYPE_VNODE Dispatch Source Vnode Event Flags
dispatch_source_get_handle

返回與指定source關聯的底層系統句柄。

這個返回的句柄是一個對source監控的底層系統對象的引用。

返回值

這個返回值根據source的類型不一樣而不一樣,它回事如下句柄中的一種:

| DISPATCH_SOURCE_TYPE_MACH_SEND | mach port (mach_port_t) |
| DISPATCH_SOURCE_TYPE_MACH_RECV | mach port (mach_port_t) |
| DISPATCH_SOURCE_TYPE_PROC | process identifier (pid_t) |
| DISPATCH_SOURCE_TYPE_READ | file descriptor (int) |
| DISPATCH_SOURCE_TYPE_SIGNAL | signal number (int) |
| DISPATCH_SOURCE_TYPE_VNODE | file descriptor (int) |
| Dispatch Source Memory Pressure Event Flags | file descriptor (int) |

dispatch_source_merge_data

合併數據到GCD source,這個source的類型爲DISPATCH_SOURCE_TYPE_DATA_ADD或者DISPATCH_SOURCE_TYPE_DATA_OR,而後將事件處理提交到目標隊列。

你的程序使用這個函數來處理DISPATCH_SOURCE_TYPE_DATA_ADD類型或DISPATCH_SOURCE_TYPE_DATA_OR類型的事件

參數

value

使用邏輯或、邏輯與組合的source類型。傳0沒有任何做用,也不會提交處理block。

dispatch_get_current_queue爲何被廢棄

有兩個串行隊列

dispatch_queue_t queueA = dispatch_queue_create("com.mikezh.queueA", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueB = dispatch_queue_create("com.mikezh.queueB", DISPATCH_QUEUE_SERIAL);

下面會發生死鎖:

dispatch_sync(queueA, ^{
    dispatch_sync(queueB, ^{
        dispatch_block_t block = ^{
            //do something
        };
        if (dispatch_get_current_queue() == queueA) {
            block();
        }else{
            dispatch_sync(queueA, block); // 程序崩潰在這裏
        }
    });
});

爲何會這裏會發生死鎖:
首先這裏的隊列嵌套關係以下所示:

咱們使用

if (dispatch_get_current_queue() == queueA)

進行判斷的目的是爲了防止在當前隊列是queueA的狀況下,向queueA中添加同步執行的block,由於這樣會發生死鎖。可是隊列嵌套關係代表,當前所在的隊列queueB被queueA嵌套,可是糟糕的是:dispatch_get_current_queue函數只能返回最內層的隊列queueB,因此這個判斷的結果不能讓咱們完成起初的目的了。

這裏已經充分說明了dispatch_get_current_queue的弊端:咱們想獲知block執行的環境是否被某個隊列嵌套來避免死鎖,而它只是簡單地只拿到最內層的隊列的功能沒法解決這個問題。

在說一些解決的方法以前,看一個上面一個功能的等價寫法(若是不理解能夠查看dispatch_set_target_queue部分):

dispatch_set_target_queue(queueB, queueA);
dispatch_sync(queueB, ^{
    dispatch_block_t block = ^{
        //do something
    };
    if (dispatch_get_current_queue() == queueA) {
        block();
    }else{
        dispatch_sync(queueA, block); // 程序崩潰在這裏
    }
});

依然是死鎖的。

下面咱們來介紹一種可是若是使用specific來判斷當前的隊列,就不會死鎖:

dispatch_set_target_queue(queueB, queueA);
    
static int specificKey;
CFStringRef specificValue = CFSTR("at hierarchy under queueA");
dispatch_queue_set_specific(queueA,
                            &specificKey,
                            (void*)specificValue,
                            (dispatch_function_t)CFRelease);

dispatch_sync(queueB, ^{
    dispatch_block_t block = ^{
        //do something
    };
    CFStringRef retrievedValue = dispatch_get_specific(&specificKey);
    if (retrievedValue == specificValue) {
        block(); // 程序走的這條分支,而不是下面的
    } else {
        dispatch_sync(queueA, block);
    }
});

咱們能夠經過dispatch_get_specific函數準確得知在隊列層級關係中是否存在specificKey指定的值,也就是說加入根據根據指定的鍵獲取不到關聯數據,那麼系統會沿着層級體系向上查找,知道找到數據或到達根隊列位置。這裏要指出的是,層級裏地位最高的那個隊列老是全局併發隊列。這個過程以下圖所示:

因此上面的代碼執行到dispatch_get_specific時會在queueA找到以前設定好的值,而後返回。這時隊列的層級關係中得知存在queueA隊列,直接調用block(),而再也不走下面的分支dispatch_sync(queueA, block);,從而有效避免了死鎖。

相關文章
相關標籤/搜索