浅谈dispatch_barrier_(a)sync

最近在使用dispatch_barrier_async时踩到了坑,随即仔细了解了一下dispatch_barrier_async,发现了不少有意思的事情,下面且听我一一道来。

平常的开发过程中,应该有不少童鞋遇到过这样的需求:分别有三个异步任务A、B、C,要求任务A、B全部执行完毕之后才执行C;面对这个需求,Dispatch Group是很多人的选择,而为了追求更简便的操作,dispatch_barrier_async应该更受青睐。
好了,既然决定使用dispatch_barrier_async,那么来吧,说干就干,很简单的几行代码,马上写出来了:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"任务A");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务B");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"任务C");
    });

好了,美滋滋的喝口饮料,等待程序执行;这么简单的东西,还能出错?搞笑呢是吧!哼哼,等着看吧,肯定是打印A、B后,最后打印出执行C。刚喝完一口饮料,看到打印结果,顿时喷了出来......

2018-03-17 15:19:16.231556+0800 dispatch_barriar_test[4274:331672] 任务C
2018-03-17 15:19:16.231569+0800 dispatch_barriar_test[4274:331836] 任务A
2018-03-17 15:19:16.231569+0800 dispatch_barriar_test[4274:331833] 任务B

啊?眼花了?编译器疯了?还是...我疯了?
看了下代码,创建并发队列,然后添加任务A、B,最后一个dispatch_barrier_async进行同步节点执行任务C,没问题啊,这怎么回事儿?跟想象的不一样,赶紧查了下该方法的官方注释,发现了一个“坑爹”的解释:



翻译过来的意思就是:
如果提交的是全局并发队列或未使用DISPATCH_QUEUE_CONCURRENT属性创建的队列(意即创建了一个串行队列)时,dispatch_barrier 与dispatch_async()/ dispatch_sync()API提交的block的行为相同。
也就是使用dispatch_barrier时,是不能使用dispatch_get_global_queue的,赶紧换成dispatch_queue_create创建一个并发队列,如下:

    dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任务A");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务B");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"任务C");
    });

这下正常了,不经意的一个小问题,导致大问题的出现,谨记谨记。

查看官方文档时候,发现了dispatch_barrier_async和dispatch_barrier_sync两个方法,以前只是使用过dispatch_barrier_async方法,而dispatch_barrier_sync却比较陌生,于是比较了两个方法的功能,又有了新的发现。

首先使用dispatch_barrier_async进行测试

    dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    dispatch_barrier_async(queue, ^{
        sleep(3);
        NSLog(@"barrier");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务4");
    });

打印结果如下:

2018-03-17 15:40:18.736130+0800 dispatch_barriar_test[4542:365066] 任务2
2018-03-17 15:40:18.736130+0800 dispatch_barriar_test[4542:365062] 任务1
2018-03-17 15:40:21.737863+0800 dispatch_barriar_test[4542:365062] barrier
2018-03-17 15:40:21.738172+0800 dispatch_barriar_test[4542:365062] 任务4

然后使用dispatch_barrier_sync测试

    dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    dispatch_barrier_sync(queue, ^{
        sleep(3);
        NSLog(@"barrier");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务4");
    });

咦?怎么一样?难道两个没有区别?不会的,苹果怎么可能给同一个API两个不同的名称?既然不同肯定有区别。接着实验,还是先实验dispatch_barrier_async:

    dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    dispatch_barrier_async(queue, ^{
        sleep(3);
        NSLog(@"barrier");
    });
    NSLog(@"来到了这里");
    dispatch_async(queue, ^{
        NSLog(@"任务4");
    });

打印结果(注意NSLog(@"来到了这里");这行代码的执行):

2018-03-17 15:45:08.918462+0800 dispatch_barriar_test[4647:374019] 任务1
2018-03-17 15:45:08.918462+0800 dispatch_barriar_test[4647:373957] 来到了这里
2018-03-17 15:45:08.918462+0800 dispatch_barriar_test[4647:374023] 任务2
2018-03-17 15:45:11.919786+0800 dispatch_barriar_test[4647:374023] barrier
2018-03-17 15:45:11.920103+0800 dispatch_barriar_test[4647:374023] 任务4

接着实验dispatch_barrier_sync:

    dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任务1");
    });
    dispatch_async(queue, ^{
        NSLog(@"任务2");
    });
    dispatch_barrier_sync(queue, ^{
        sleep(3);
        NSLog(@"barrier");
    });
    NSLog(@"来到了这里");
    dispatch_async(queue, ^{
        NSLog(@"任务4");
    });

打印结果:

2018-03-17 15:48:13.325138+0800 dispatch_barriar_test[4714:380014] 任务1
2018-03-17 15:48:13.325141+0800 dispatch_barriar_test[4714:380011] 任务2
2018-03-17 15:48:16.325626+0800 dispatch_barriar_test[4714:379926] barrier
2018-03-17 15:48:16.325873+0800 dispatch_barriar_test[4714:379926] 来到了这里
2018-03-17 15:48:16.326083+0800 dispatch_barriar_test[4714:380011] 任务4

区别来了,"来到了这里"这句话执行时机不同,async不会阻塞主线程后面的代码执行,而sync则是会将本身block中的任务执行完毕后,程序才会接着执行sync之后的代码。总结如下:
dispatch_barrier_sync和dispatch_barrier_async的共同点:
1、都会等待在它前面插入队列的任务(A、B)先执行完
2、都会等待他们自己的任务(barrier)执行完再执行后面的任务(C)
dispatch_barrier_sync和dispatch_barrier_async的不共同点:
在将任务插入到queue的时候,dispatch_barrier_sync需要等待自己的任务(barrier)结束之后才会继续程序,然后插入被写在它后面的任务(C),然后执行任务(C)
而dispatch_barrier_async将自己的任务(barrier)插入到queue之后,不会等待自己的任务结束,程序继续执行,会继续把后面的任务(C)插入到queue。
所以,dispatch_barrier_async的不等待(异步)特性体现在将任务插入队列的过程,它的等待特性体现在任务真正执行的过程。

附录(也算是追加)

也许会对并行、串行、异步、同步有所理解不深。
并行:就是队列里面的任务(代码块,block)不是一个个执行,而是并发执行,也就是可以同时执行的意思
串行:队列里面的任务一个接着一个执行,要等前一个任务结束,下一个任务才可以执行
异步:具有新开线程的能力
同步:不具有新开线程的能力,只能在当前线程执行任务

那么,如果他们相互串起来,会怎么样呢?
并行+异步:就是真正的并发,新开有有多个线程处理任务,任务并发执行(不按顺序执行)
串行+异步:新开一个线程,任务一个接一个执行,上一个任务处理完毕,下一个任务才可以被执行
并行+同步:不新开线程,任务一个接一个执行
串行+同步:不新开线程,任务一个接一个执行

参考文献:https://www.cnblogs.com/ziyi--caolu/p/4900650.html
http://blog.csdn.net/u013046795/article/details/47057585

你可能感兴趣的:(浅谈dispatch_barrier_(a)sync)