iOS GCD全析(三)

本文摘录自《Objective-C高级编程》一书,附加一些自己的理解,作为对GCD的总结。



此篇主要包含以下几个方面:

  • Dispatch Group
    • dispatch_group_t
    • dispatch_group_create()
    • dispatch_group_async()
    • dispatch_group_notify()
    • dispatch_group_wait()
    • dispatch_group_enter() / dispatch_group_leave()
  • dispatch_barrier_async



Dispatch Group

当我们向队列中添加的多个任务全部执行完毕后想最后再执行一次“总结性”的任务,这种需求经常出现。虽然我们可以交给串行队列来处理,串行队列方便我们控制其执行顺序,但是串行队列不是多线程同时执行,效率低。即使可以达到这种效果,源代码也会变得颇为复杂。

在此种情况下使用Dispatch Group。例如下面源代码为:追加3个Block到Global Dispatch Queue,这些Block如果全部执行完毕,就会执行Main Dispatch Queue中结束处理用的Block。


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
    NSLog(@"任务1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任务2");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任务3");
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"done");
});

控制台:

因为向Global Dispatch Queue 即 Concurrent Dispatch Queue追加处理,多个线程并行执行,所以追加处理的执行顺序不定。执行时会发生变化,但是此执行结果的done一定是最后输出的。

无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可监视这些处理执行的结束。一旦检测到所有处理执行结束,就可将"总结性"的处理追加到Dispatch Queue中。这就是使用Dispatch Group的原因。

dispatch_group_async 函数与 dispatch_async 函数相同,都追加Block到指定的Dispatch Queue中。与 dispatch_async 函数不同的是指定生成的Dispatch Group为第一个参数。指定的Block 属于指定的Dispatch Group。

在追加到Dispatch Group中的处理全部执行结束时,该源代码中使用的dispatch_group_notify函数会将执行的Block 追加到Dispatch Queue中,将第一个参数指定为要监视的Dispatch Group。在追加到该Dispatch Group的全部处理执行结束时,将第三个参数的Block追加到第二个参数的Dispatch Queue中。在dispatch_group_notify函数中不管指定什么样的Dispatch Queue,属于Dispatch Group的全部处理在追加指定的Block时都已执行结束。

dispatch_group_wait

另外,在Dispatch Group 中也可以使用dispatch_group_wait函数仅等待全部处理执行结束。


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
    NSLog(@"任务1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任务2");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任务3");
});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_async(group, queue, ^{
    NSLog(@"done");
});

控制台:

dispatch_group_wait 函数的第二个参数指定为等待的时间(超时)。它属于dispatch_ time_t 类型的值。该源代码使用DISPATCH_TIME_FOREVER,意味着永久等待。只要属于Dispatch Group 的处理尚未执行结束,就会一直等待,中途不能取消。

如果 dispatch_group_wait 函数的返回值不为0,就意味着虽然经过了指定的时间,但属于Dispatch Group的某一个处理还在执行中。如果返回值为0,那么全部处理执行结束。当等待时间为DISPATCH_TIME_FOREVER、由dispatch_group_wait 函数返回时,由于属于Dispatch Group的处理必定全部执行结束,因此返回值恒为0。

这里的“等待”是什么意思呢?这意味着一旦调用 dispatch_group_wait 函数,该函数就处于调用的状态而不返回。即执行dispatch_group_wait函数的现在的线程(当前线程)停止。在经过 dispatch_group_wait 函数中指定的时间或属于指定Dispatch Group的处理全部执行结束之前,执行该函数的线程停止。

指定DISPATCH_TIME_NOW,则不用任何等待即可判定属于Dispatch Group的处理是否执行结束。

在主线程的RunLoop的每次循环中,可检查执行是否结束,从而不耗费多余的等待时间,虽然这样也可以,但一般在这种情形下,还是推荐用 dispatch_group_notify 函数追加结束处理到Main Dispatch Queue中。这是因为 dispatch_ group_notify 函数可以简化源代码。

对比 dispatch_ group_notify 函数和 dispatch_group_wait 函数的不同

首先是 dispatch_ group_notify 函数


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
    sleep(1);
    NSLog(@"任务1");
});

dispatch_group_async(group, queue, ^{
    sleep(1);
    NSLog(@"任务2");
});

dispatch_group_async(group, queue, ^{
    sleep(1);
    NSLog(@"任务3");
});

dispatch_group_notify(group, queue, ^{
    sleep(1);
    NSLog(@"任务4");
});

NSLog(@"任务5");

控制台:

可以看出,任务5最先执行,也就是说dispatch_group_notify函数是非线程阻塞的。

接下来看看 dispatch_group_wait 函数


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t group = dispatch_group_create();

dispatch_group_async(group, queue, ^{
    NSLog(@"任务1");
    sleep(1);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任务2");
    sleep(1);
});

dispatch_group_async(group, queue, ^{
    NSLog(@"任务3");
    sleep(1);
});

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

NSLog(@"任务5");

dispatch_group_async(group, queue, ^{
    NSLog(@"done");
});

控制台:

可以看出,任务5是在dispatch_group_wait函数阻塞解除之后才执行,所以dispatch_group_wait函数会阻塞线程。

总结:

  • dispatch_group_notify函数不会阻塞线程,dispatch_group_wait会阻塞线程

  • dispatch_group_notify 函数可以直接添加任务,dispatch_group_wait函数只是单纯的阻塞了线程继续向下执行,所以需要自己另行添加“总结性任务”

  • dispatch_group_notify函数中的参数queue不论是否与dispatch_group_async是相同的queue队列,都会最后执行

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"任务4");
    });
    


dispatch_group_enter / dispatch_group_leave

先看看苹果官方文档关于dispatch_group_enter的描述:

Explicitly indicates that a block has entered the group.
显式的表示一个代码块已经加入到组中。

Calling this function increments the current count of outstanding tasks in the group. Using this function (with dispatch_group_leave) allows your application to properly manage the task reference count if it explicitly adds and removes tasks from the group by a means other than using the dispatch_group_async function. A call to this function must be balanced with a call to dispatch_group_leave. You can use this function to associate a block with more than one group at the same time.
调用此函数将增加组中未完成任务的当前计数。如果不使用dispatch_group_async函数进行显式的增加或者删除组中的任务,使用此函数(配合dispatch_group_leave函数)可让你的程序正确管理任务引用计数。此函数的调用必须与dispatch_group_leave函数的调用相平衡。可以使用此函数将一个代码块与多个组同时关联。


再看看苹果官方文档关于dispatch_group_leave的描述:

Explicitly indicates that a block in the group has completed.
显式的表示组中的一个代码块已经执行完毕。

Calling this function decrements the current count of outstanding tasks in the group. Using this function (with dispatch_group_enter) allows your application to properly manage the task reference count if it explicitly adds and removes tasks from the group by a means other than using the dispatch_group_async function.
调用此函数将减少组中未完成任务的当前计数。如果不使用dispatch_group_async函数进行显式的增加或者删除组中的任务,使用此函数(配合dispatch_group_leave函数)可允许你的程序正确管理任务引用计数。

A call to this function must balance a call to dispatch_group_enter. It is invalid to call it more times than dispatch_group_enter, which would result in a negative count.
此函数的调用必须与dispatch_group_enter函数的调用相平衡。调用它的次数超过dispatch_group_enter是无效的,这会导致负计数。


总的来说dispatch_group_enter / dispatch_group_leave这两个函数在使用时是成对出现的。enter是表示要向组中添加任务(代码块),组中未完成任务计数 +1。leave表示组中有个任务完成了,组中未完成任务计数 -1。这两个方法核心功能便是操作组中的任务计数,而任务计数会影响到dispatch_group_wait函数和dispatch_group_notify函数。

下面看个例子:


dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

NSLog(@"group task start");

dispatch_group_enter(group);
dispatch_async(queue, ^{
    NSLog(@"任务1");
    sleep(3);
    dispatch_group_leave(group);
});

dispatch_group_notify(group, queue, ^{
    NSLog(@"任务全部结束!");
});

箭头指示处就是打印的时间秒数,dispatch_group_notify中的任务的执行时间比“任务1”晚了3秒,可见dispatch_group_notify将“任务1”看做了组中的任务。这就使得普通的异步函数也可以被加入到组中,方便做最后的“总结性”的处理。



dispatch_barrier_async

下面是头文件中对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.
 */

大概意思就是说:这一系列的API只对通过dispatch_queue_create()创建出来的DISPATCH_QUEUE_CONCURRENT并发队列有效。如果是其它的队列,比如全局队列(global queue)或者所有不是通过dispatch_queue_create()创建出来的DISPATCH_QUEUE_CONCURRENT并发队列,dispatch_barrier_async / dispatch_barrier_sync就会变成跟dispatch_async / dispatch_sync一样,失去其特殊性,也就是说dispatch_barrier_async没有“栅栏”的作用了。

下面举一个例子来说明dispatch_barrier_async的具体功能:

// 通过dispatch_queue_create创建出来的DISPATCH_QUEUE_CONCURRENT并发队列
dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);

__block int num = 10;

dispatch_async(queue, ^{
    NSLog(@"读取任务1 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"读取任务2 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"读取任务3 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"读取任务4 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"读取任务5 num=%d",num);
});

dispatch_barrier_async(queue, ^{
    for (int i = 0; i < 10; i++) {
        NSLog(@"第%d次写入", i+1);
    }
    num = 20;
});

dispatch_async(queue, ^{
    NSLog(@"读取任务6 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"读取任务7 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"读取任务8 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"读取任务9 num=%d",num);
});

dispatch_async(queue, ^{
    NSLog(@"读取任务10 num=%d",num);
});

执行结果:

我们由此可以看出,在dispatch_barrier_async之前添加到队列的读取任务1-5会并发执行,并且dispatch_barrier_async添加到队列的写入任务会等到这些前面的读取任务1-5全部执行完才会执行。在执行dispatch_barrier_async中的任务时,队列中其它所有任务都会停下来等待它,只有dispatch_barrier_async中的任务全部完成时,后面的任务才会继续,而且依然是并发执行。

简单来说,在dispatch_barrier_async之前添加进去的任务会并发执行,在dispatch_barrier_async之后添加进去的也会并发执行,但是会被dispatch_barrier_async像栅栏一样给隔开。在dispatch_barrier_async中的任务执行时具有排他性,其它所有任务都得停下来等待它执行完毕。这就为读 / 写保护提供了良好的工具,在写入时没有人来竞争资源。


另外,dispatch_barrier_async的函数名中有async,我们会猜到应该有另一个函数叫做dispatch_barrier_sync,确实有这个函数,但是这个函数会造成线程阻塞,像dispatch_group_wait一样。

dispatch_barrier_async函数不会阻塞线程,所以主线程的任务不会受到阻碍

dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"读取任务1");
});

dispatch_barrier_async(queue, ^{
    NSLog(@"写入");
});

NSLog(@"主线程的任务");

dispatch_async(queue, ^{
    NSLog(@"读取任务2");
});

执行结果:

dispatch_barrier_sync函数会阻塞线程,所以主线程的任务会被阻挡到后面去

dispatch_queue_t queue = dispatch_queue_create("com.example", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    NSLog(@"读取任务1");
});

dispatch_barrier_sync(queue, ^{
    NSLog(@"写入");
});

NSLog(@"主线程的任务");

dispatch_async(queue, ^{
    NSLog(@"读取任务2");
});

执行结果:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容