本文是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代码样例
点个✨