GCD(二) dispatch_barrier

本文是GCD多線程編程中dispatch_barrier內容的小結,經過本文,你能夠了解到:git

  • dispatch_barrier的來源
  • 如何使用dispatch_barrier_async/dispatch_barrier_sync函數處理柵欄任務
  • dispatch_barrier_async/dispatch_barrier_sync函數使用效果的對比
  • 如何使用dispatch_barrier_async實現多讀單寫

dispatch_barrier的來源

經過上一篇文章GCD(一) 隊列、任務、串行、併發的講解,咱們瞭解到,併發隊列可讓你追加到隊列的block併發執行,而不須要等待前面入隊的block完成運行。可是這樣又會引起一個問題,若是併發隊列容許全部的block同時執行,那麼他們爲何被稱爲隊列(FIFO)呢,它不更像一個能夠加入併發執行block的堆嗎?github

針對上面的問題,GCD中提供了Dispatch_barrier系統的API,俗稱柵欄,使用dispatch_barrier_sync()或者dispatch_barrier_async()入隊的block,會等到全部的以前入隊的block執行完成後纔開始執行。除此以外,在barrier block後面入隊的全部的block,會等到到barrier block自己已經執行完成以後才繼續執行。它就像咱們平時早上上班擠地鐵限流的樣子,一位地鐵工做人員拿着一個指示牌,表示在他排在以前的人流(無序並行)進站以後,他以後的人流才能夠進站。就是由於這個柵欄,併發隊列的行爲看起來就像隊列了。編程

測試代碼在這bash

dispatch_barrier_async

/*! * @functiongroup Dispatch Barrier API * The dispatch barrier API is a mechanism for submitting barrier blocks to a * dispatch queue, analogous to the dispatch_async()/dispatch_sync() API. * It enables the implementation of efficient reader/writer schemes. * Barrier blocks only behave specially when submitted to queues created with * the DISPATCH_QUEUE_CONCURRENT attribute; on such a queue, a barrier block * will not run until all blocks submitted to the queue earlier have completed, * and any blocks submitted to the queue after a barrier block will not run * until the barrier block has completed. * When submitted to a a global queue or to a queue not created with the * DISPATCH_QUEUE_CONCURRENT attribute, barrier blocks behave identically to * blocks submitted with the dispatch_async()/dispatch_sync() API. */

/*! * @function dispatch_barrier_async * * @abstract * Submits a barrier block for asynchronous execution on a dispatch queue. * * @discussion * Submits a block to a dispatch queue like dispatch_async(), but marks that * block as a barrier (relevant only on DISPATCH_QUEUE_CONCURRENT queues). * * See dispatch_async() for details. * * @param queue * The target dispatch queue to which the block is submitted. * The system will hold a reference on the target queue until the block * has finished. * The result of passing NULL in this parameter is undefined. * * @param block * The block to submit to the target dispatch queue. This function performs * Block_copy() and Block_release() on behalf of callers. * The result of passing NULL in this parameter is undefined. */
複製代碼

從它的官方註釋中咱們能夠知道:多線程

  1. dispatch_barrier 是一個相似於dispatch_async()/dispatch_sync()的API,它能夠將barrier block提交到隊列中,barrier block 只有提交到自定義的併發隊列中才能真正的當作一個柵欄,它在這裏起到一個承上啓下的做用,只有比它(barrier block)先提交到自定義併發隊列的block所有執行完成,它纔會去執行,等它執行完成,在它以後添加的block纔會繼續往下執行。
  2. dipatch_barrier block沒有被提交到自定義的串行隊列中,它與dispatch_async()/dispatch_sync()的做用是同樣的。

咱們經過一些代碼去驗證一下:併發

##pragma mark - dispatch_barrier_async + 自定義併發隊列
/* * 特色: * 1.barrier以前的任務併發執行,barrier以後的任務在barrier任務完成以後併發執行 * 2.會開啓新線程執行任務 * 3.不會阻塞當前線程(主線程) */
- (IBAction)executeBarrierAsyncCustomConcurrentQueueTask:(UIButton *)sender {
    
    NSLog(@"CurrentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"---begin---");
    
    NSLog(@"追加任務1");
    dispatch_async(self.concurrentQueue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"追加任務2");
    dispatch_async(self.concurrentQueue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"追加barrier_async任務");
    dispatch_barrier_async(self.concurrentQueue, ^{
        // 追加barrier任務
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"barrier---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"追加任務3");
    dispatch_async(self.concurrentQueue, ^{
        // 追加任務3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"追加任務4");
    dispatch_async(self.concurrentQueue, ^{
        // 追加任務4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    NSLog(@"---end---");
    NSLog(@"*********************************************************");
}
複製代碼

執行結果以下:異步

2019-04-23 16:14:35.900776+0800 GCD(二) dispatch_barrier[18819:3551551] CurrentThread---<NSThread: 0x600002b76980>{number = 1, name = main}
2019-04-23 16:14:35.900984+0800 GCD(二) dispatch_barrier[18819:3551551] ---begin---
2019-04-23 16:14:35.901171+0800 GCD(二) dispatch_barrier[18819:3551551] 追加任務1
2019-04-23 16:14:35.901355+0800 GCD(二) dispatch_barrier[18819:3551551] 追加任務2
2019-04-23 16:14:35.901596+0800 GCD(二) dispatch_barrier[18819:3551551] 追加barrier_async任務
2019-04-23 16:14:35.901789+0800 GCD(二) dispatch_barrier[18819:3551551] 追加任務3
2019-04-23 16:14:35.902093+0800 GCD(二) dispatch_barrier[18819:3551551] 追加任務4
2019-04-23 16:14:35.902378+0800 GCD(二) dispatch_barrier[18819:3551551] ---end---
2019-04-23 16:14:35.902644+0800 GCD(二) dispatch_barrier[18819:3551551] *********************************************************
2019-04-23 16:14:37.904283+0800 GCD(二) dispatch_barrier[18819:3551647] 1---<NSThread: 0x600002b11bc0>{number = 3, name = (null)}
2019-04-23 16:14:37.904283+0800 GCD(二) dispatch_barrier[18819:3552871] 2---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019-04-23 16:14:39.909809+0800 GCD(二) dispatch_barrier[18819:3551647] 1---<NSThread: 0x600002b11bc0>{number = 3, name = (null)}
2019-04-23 16:14:39.909810+0800 GCD(二) dispatch_barrier[18819:3552871] 2---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019-04-23 16:14:41.914667+0800 GCD(二) dispatch_barrier[18819:3552871] barrier---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019-04-23 16:14:43.917811+0800 GCD(二) dispatch_barrier[18819:3552871] barrier---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019-04-23 16:14:45.921840+0800 GCD(二) dispatch_barrier[18819:3552871] 3---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019-04-23 16:14:45.921847+0800 GCD(二) dispatch_barrier[18819:3551647] 4---<NSThread: 0x600002b11bc0>{number = 3, name = (null)}
2019-04-23 16:14:47.927349+0800 GCD(二) dispatch_barrier[18819:3552871] 3---<NSThread: 0x600002b11cc0>{number = 4, name = (null)}
2019-04-23 16:14:47.927373+0800 GCD(二) dispatch_barrier[18819:3551647] 4---<NSThread: 0x600002b11bc0>{number = 3, name = (null)}
複製代碼

由此咱們能夠看出:async

在barrier_async任務以前加入隊列的任務,會在barrier任務以前併發執行,而且開闢了2條新線程去執行,barrier任務在任務一、任務2執行完成以後執行,執行完成以後,後續添加的任務才繼續往下執行,而且dispatch_async並無阻塞當前的主線程ide

dispatch_barrier_sync

咱們將上一步的測試代碼中的dispatch_barrier_async改成dispatch_barrier_sync方法去執行,獲得的log以下:函數

2019-04-23 16:18:04.874397+0800 GCD(二) dispatch_barrier[18819:3551551] CurrentThread---<NSThread: 0x600002b76980>{number = 1, name = main}
2019-04-23 16:18:04.874601+0800 GCD(二) dispatch_barrier[18819:3551551] ---begin---
2019-04-23 16:18:04.874758+0800 GCD(二) dispatch_barrier[18819:3551551] 追加任務1
2019-04-23 16:18:04.874929+0800 GCD(二) dispatch_barrier[18819:3551551] 追加任務2
2019-04-23 16:18:04.875118+0800 GCD(二) dispatch_barrier[18819:3551551] 追加barrier_sync任務
2019-04-23 16:18:06.880055+0800 GCD(二) dispatch_barrier[18819:3552872] 1---<NSThread: 0x600002b12100>{number = 5, name = (null)}
2019-04-23 16:18:06.880102+0800 GCD(二) dispatch_barrier[18819:3562466] 2---<NSThread: 0x600002b288c0>{number = 6, name = (null)}
2019-04-23 16:18:08.885244+0800 GCD(二) dispatch_barrier[18819:3552872] 1---<NSThread: 0x600002b12100>{number = 5, name = (null)}
2019-04-23 16:18:08.885244+0800 GCD(二) dispatch_barrier[18819:3562466] 2---<NSThread: 0x600002b288c0>{number = 6, name = (null)}
2019-04-23 16:18:10.886126+0800 GCD(二) dispatch_barrier[18819:3551551] barrier---<NSThread: 0x600002b76980>{number = 1, name = main}
2019-04-23 16:18:12.887616+0800 GCD(二) dispatch_barrier[18819:3551551] barrier---<NSThread: 0x600002b76980>{number = 1, name = main}
2019-04-23 16:18:12.887776+0800 GCD(二) dispatch_barrier[18819:3551551] 追加任務3
2019-04-23 16:18:12.887907+0800 GCD(二) dispatch_barrier[18819:3551551] 追加任務4
2019-04-23 16:18:12.888021+0800 GCD(二) dispatch_barrier[18819:3551551] ---end---
2019-04-23 16:18:12.888121+0800 GCD(二) dispatch_barrier[18819:3551551] *********************************************************
2019-04-23 16:18:14.888428+0800 GCD(二) dispatch_barrier[18819:3552872] 4---<NSThread: 0x600002b12100>{number = 5, name = (null)}
2019-04-23 16:18:14.888461+0800 GCD(二) dispatch_barrier[18819:3562492] 3---<NSThread: 0x600002b29e00>{number = 7, name = (null)}
2019-04-23 16:18:16.893977+0800 GCD(二) dispatch_barrier[18819:3562492] 3---<NSThread: 0x600002b29e00>{number = 7, name = (null)}
2019-04-23 16:18:16.893977+0800 GCD(二) dispatch_barrier[18819:3552872] 4---<NSThread: 0x600002b12100>{number = 5, name = (null)}
複製代碼

經過log能夠看到,barrier_syncbarrier_async同樣,均可以在併發隊列中起到柵欄的做用。可是2個方法仍是有些不一樣的,下文中咱們會詳細講解

dispatch_barrier_async與dispatch_barrier_sync的對比

經過上面的代碼測試,咱們能夠發現,barrier_asyncbarrier_sync的區別僅僅在於,barrier_sync會阻塞它以後的任務的入隊,必須等到barrier_sync任務執行完畢,纔會把後面的異步任務添加到併發隊列中,而barrier_async不須要等自身的block執行完成,就能夠把後面的任務添加到隊列中。

dispatch_barrier_sync與死鎖

因爲只有使用自定義併發隊列時,dispatch_barrier方式添加的任務,才能起到柵欄的做用,添加到其它隊列的狀況下,dispatch_barrier_async/dispatch_barrier_syncdispatch_async/dispatch_sync的做用是同樣的,因此,當在串行隊列中使用dispatch_barrier_sync時,一樣的也有可能死鎖,因此,咱們在日常開發中要謹慎使用dispatch_barrier_sync

dispatch_barrier_async實現多讀單寫

假如說咱們在內存維護一個字典或者是一個DB文件,有多個讀者或者寫者都要操做這個共享數據,咱們爲了實現這個多讀單寫的模型,就須要考慮多線程對這個數據的訪問問題,咱們首先要解決讀者與讀者應該是併發的讀取,表明了多讀的含義,其次呢,讀者與讀者應該是互斥的,好比說,有讀者在讀取數據的時候,就不能有些的線程去寫數據,因此說,讀者與寫者要互斥,其次呢,寫者與寫者也要互斥,有一個寫線程在寫數據,那麼另外一個寫線程就不能去寫數據,不然會致使程序異常或者程序錯亂,要知足一下三點,其實咱們可使用dispatch_barrier_async來實現多讀單寫

  • 讀者與讀者併發
  • 讀者與寫者互斥
  • 寫者與寫者互斥

咱們再經過一副圖來看一下多讀單寫的具體實現流程

假如說有多個讀處理同時或者併發執行的話,而後寫處理須要跟讀處理互斥操做,在寫處理完成以後呢,而後能夠再次進行讀取處理,而dispatch_barrier_async正好就爲咱們實現了一個多讀單寫的模型,也就是當咱們的讀者在進行讀處理的時候,其它的讀者也能夠額進行讀取,可是此時,不能進行寫,若是在寫操做的過程當中,有其它的讀處理,那麼這些讀處理,就只能在寫操做完成以後才能夠執行。

  1. 咱們首先定義一個類ZEDMultiReadSingleWriteHandler,而後在類中定義2個屬性
/** 併發隊列 */
@property (nonatomic, strong) dispatch_queue_t concurrentQueue;

/** 多讀單寫的數據容器,可能在不一樣的線程中訪問 */
@property (nonatomic, strong) NSMutableDictionary *dict;
複製代碼

第一個屬性是一個自定義的併發隊列,用於使用dispatch_barrier_async的方式進行寫操做。

第二個屬性是一個數據存儲的容器,可能會在不一樣的線程中訪問。

  1. 在類的初始化方法裏,建立併發隊列與全局容器字典
- (instancetype)init {
    self = [super init];
    if (self) {
        self.concurrentQueue = dispatch_queue_create("zed.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
        self.dict = [NSMutableDictionary dictionary];
    }
    return self;
}
複製代碼
  1. 而後咱們定義2個方法,用於給外部調用的讀操做與寫操做
- (id)dataForKey:(NSString *)key;
- (void)setData:(id)data forKey:(NSString *)key;
複製代碼
  1. 對於讀操做而言,多個讀操做能夠併發執行,併發隊列的特性就是容許提交的任務併發執行,咱們這裏提交的任務是經過一個key去字典中獲取對象,因爲這個獲取操做是須要馬上同步返回結果的,因此咱們要經過dispatch_sync這個函數來進行調用,同步馬上返回這個調用結果,同步到這個併發隊列中,就能夠容許多個線程同時讀取,好比說,dataForKey這個方法能夠在A線程中調用,也能夠在B線程中調用,當A線程與B線程併發來訪問這個同一個Key的時候,因爲併發隊列的性質,就能夠保證他們同時併發讀取某一個key的值,同時是同步調用,因此不論是哪個線程,均可以經過這種同步方式馬上返回調用結果。

    - (id)dataForKey:(NSString *)key {
        __block id data;
        //同步讀取指定數據
        dispatch_sync(self.concurrentQueue, ^{
            data = [self.dict objectForKey:key];
        });
        return data;
    }
    複製代碼
  2. 寫的操做就是就經過dispatch_barrier_async到一個併發隊列當中去進行寫,而後咱們經過key把對應的值寫進去

    - (void)setData:(id)data forKey:(NSString *)key {
        // 異步柵欄調用設置數據
        dispatch_barrier_async(self.concurrentQueue, ^{
            [self.dict setObject:data forKey:key];
        });
    }
    複製代碼

若是文中有錯誤的地方,或者與你的想法相悖的地方,請在評論區告知我,我會繼續改進,若是你以爲這個篇文章總結的還不錯,麻煩動動小手,給個人文章與Git代碼樣例點個✨

相關文章
相關標籤/搜索