本文是GCD多線程編程中dispatch_barrier
內容的小結,經過本文,你能夠了解到:git
柵欄
任務經過上一篇文章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
/*! * @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. */ 複製代碼
從它的官方註釋中咱們能夠知道:markdown
dispatch_barrier
是一個相似於dispatch_async()/dispatch_sync()
的API,它能夠將barrier block
提交到隊列中,barrier block
只有提交到自定義的併發隊列中才能真正的當作一個柵欄
,它在這裏起到一個承上啓下的做用,只有比它(barrier block
)先提交到自定義併發隊列的block所有執行完成,它纔會去執行,等它執行完成,在它以後添加的block纔會繼續往下執行。- 當
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)} 複製代碼
由此咱們能夠看出:異步
在barrier_async任務以前加入隊列的任務,會在barrier任務以前併發執行,而且開闢了2條新線程去執行,barrier任務在任務一、任務2執行完成以後執行,執行完成以後,後續添加的任務才繼續往下執行,而且dispatch_async
並無阻塞當前的主線程async
咱們將上一步的測試代碼中的dispatch_barrier_async
改成dispatch_barrier_sync
方法去執行,獲得的log以下:ide
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_sync
與barrier_async
同樣,均可以在併發隊列中起到柵欄
的做用。可是2個方法仍是有些不一樣的,下文中咱們會詳細講解
經過上面的代碼測試,咱們能夠發現,barrier_async
與barrier_sync
的區別僅僅在於,barrier_sync
會阻塞它以後的任務的入隊,必須等到barrier_sync任務執行完畢,纔會把後面的異步任務添加到併發隊列中,而barrier_async
不須要等自身的block執行完成,就能夠把後面的任務添加到隊列中。
因爲只有使用自定義併發隊列時,dispatch_barrier
方式添加的任務,才能起到柵欄
的做用,添加到其它隊列的狀況下,dispatch_barrier_async/dispatch_barrier_sync
與dispatch_async/dispatch_sync
的做用是同樣的,因此,當在串行隊列中使用dispatch_barrier_sync
時,一樣的也有可能死鎖,因此,咱們在日常開發中要謹慎使用dispatch_barrier_sync
假如說咱們在內存維護一個字典或者是一個DB文件,有多個讀者或者寫者都要操做這個共享數據,咱們爲了實現這個多讀單寫的模型,就須要考慮多線程對這個數據的訪問問題,咱們首先要解決讀者與讀者應該是併發的讀取,表明了多讀的含義,其次呢,讀者與讀者應該是互斥的,好比說,有讀者在讀取數據的時候,就不能有些的線程去寫數據,因此說,讀者與寫者要互斥,其次呢,寫者與寫者也要互斥,有一個寫線程在寫數據,那麼另外一個寫線程就不能去寫數據,不然會致使程序異常或者程序錯亂,要知足一下三點,其實咱們可使用dispatch_barrier_async
來實現多讀單寫
咱們再經過一副圖來看一下多讀單寫的具體實現流程
假如說有多個讀處理同時或者併發執行的話,而後寫處理須要跟讀處理互斥操做,在寫處理完成以後呢,而後能夠再次進行讀取處理,而dispatch_barrier_async
正好就爲咱們實現了一個多讀單寫的模型,也就是當咱們的讀者在進行讀處理的時候,其它的讀者也能夠額進行讀取,可是此時,不能進行寫,若是在寫操做的過程當中,有其它的讀處理,那麼這些讀處理,就只能在寫操做完成以後才能夠執行。
ZEDMultiReadSingleWriteHandler
,而後在類中定義2個屬性/** 併發隊列 */ @property (nonatomic, strong) dispatch_queue_t concurrentQueue; /** 多讀單寫的數據容器,可能在不一樣的線程中訪問 */ @property (nonatomic, strong) NSMutableDictionary *dict; 複製代碼
第一個屬性是一個自定義的併發隊列,用於使用dispatch_barrier_async
的方式進行寫操做。
第二個屬性是一個數據存儲的容器,可能會在不一樣的線程中訪問。
- (instancetype)init { self = [super init]; if (self) { self.concurrentQueue = dispatch_queue_create("zed.concurrent.queue", DISPATCH_QUEUE_CONCURRENT); self.dict = [NSMutableDictionary dictionary]; } return self; } 複製代碼
- (id)dataForKey:(NSString *)key; - (void)setData:(id)data forKey:(NSString *)key; 複製代碼
對於讀操做而言,多個讀操做能夠併發執行,併發隊列的特性就是容許提交的任務併發執行,咱們這裏提交的任務是經過一個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; } 複製代碼
寫的操做就是就經過dispatch_barrier_async
到一個併發隊列當中去進行寫,而後咱們經過key把對應的值寫進去
- (void)setData:(id)data forKey:(NSString *)key { // 異步柵欄調用設置數據 dispatch_barrier_async(self.concurrentQueue, ^{ [self.dict setObject:data forKey:key]; }); } 複製代碼
若是文中有錯誤的地方,或者與你的想法相悖的地方,請在評論區告知我,我會繼續改進,若是你以爲這個篇文章總結的還不錯,麻煩動動小手,給個人文章與Git代碼樣例
點個✨