44.通过Dispatch Group机制,根据系统资源状况来执行任务

《编写高质量iOS与OS X代码的52个有效方法》--第六章 第44条
(ps:此乃读书笔记,加深记忆,仅供大家参考)


第44条:通过Dispatch Group机制,根据系统资源状况来执行任务

dispatch group是GCD的一项特性,能够把任务分组。调用者可以等待这组任务执行完毕,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知。这个功能有许多用途,其中最重要、最值得注意的用法,就是把将要并发执行的多个任务合为一组,于是调用者就可以知道这些任务何时才能全部执行完毕。

下面这个函数可以创建dispatch group:

dispatch_group_t group = dispatch_group_create();

dispatch group就是个简单的数据结构,这种结构彼此之间没什么区别,它不像派发队列,后者还有个用来区别身份的标识符。想把任务编组,有两种办法。第一种是下面这个函数:

void dispatch_group_async(dispatch_group_t group,
                          dispatch_queue_t queue,
                          dispatch_block_t block);

它是普通的dispatch_async函数的变体,比原来多一个函数,用于表示待执行的块所归属的组。还有种办法能够指定任务所属的dispatch group,那就是下面这一对函数:

dispatch_group_enter(dispatch_group_t group)
dispatch_group_leave(dispatch_group_t group)

前者能够使分组里正要执行的任务数递增,而后者则使之递减。由此可知,调用了dispatch_group_enter以后,必须又与之对应的dispatch_group_leave才行。这与引用计数(参见第29条)相似,要使用引用计数。就必须令保留操作与释放操作彼此对应,以防内存泄漏。

下面这个函数可用于等待dispatch group执行完毕:

void dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout)

此函数接受两个参数,一个是要等待的group,另一个是代表等待时间的timeout值。timeout参数表示函数在等待dispatch group执行完毕时,应该阻塞多久。如果执行dispatch group所需的时间小于timeout,则返回0,否则返回非0值。此参数也可以取常量DISPATCH_TIME_FOREVER,这表示函数会一直等待dispatch group执行完,而不会超时(time out)。
除了可以用上面那个函数等待dispatch group执行完毕之外,也可以换个办法,使用下列函数:

void dispatch_group_notify(dispatch_group_t group,
                           dispatch_queue_t queue,
                           dispatch_block_t block);

与wait函数略有不同的是:开发者可以向此函数传入块,等dispatch group执行完毕之后,块会在特定的线程上执行。加入当前此案成不应阻塞,而开发者又想在那些任务全部完成时得到通知,那么此做法就很有必要了。

如果想令数组中的每个对象都执行某项任务,并且想等待所有任务执行完毕,那么就可以使用这个GCD特性来实现。代码如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_t dispatchGroup = dispatch_group_create();

NSArray * collection;
for (id object in collection) {
    dispatch_group_async(dispatchGroup, queue, ^{
        [object description];
    });
}

dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
//Continue processing after completing tasks

若当前线程不应阻塞,则可用notify函数来取代wait:

dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
    //Continue processing after completing tasks
});

notify回调时所选用的队列,完全应该根据具体情况来定。笔者在范例代码中使用了主线程队列,这种是常见写法。也可以用自定义的串行队列或者全局并发队列。

在本例中,所有任务都派发到同一队列之中。但实际上未必一定要这样做。也可以把某些任务放在优先级高的线程上执行,同时仍然把所有任务都归入同一个dispatch group,并在执行完毕时获得通知:

dispatch_queue_t lowPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t highPriorityQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

dispatch_group_t dispatchGroup = dispatch_group_create();

NSArray * lowPriorityObject;
NSArray * highPriorityObject;
for (id object in lowPriorityObject) {
    dispatch_group_async(dispatchGroup, lowPriorityQueue, ^{
        [object description];
    });
}

for (id object in highPriorityObject) {
    dispatch_group_async(dispatchGroup, highPriorityQueue, ^{
        [object description];
    });
}


dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
    //Continue processing after completing tasks
});

除了像上面这样把任务提交到并发队列之外,也可以把任务提交至各个串行队列中,并用dispatch group跟踪其执行状况。然而,如果所有任务都排在同一个串行队列里面,那么dispatch group就用处不大了。因为此时,任务总要逐个执行,所以只需在提交完全部任务之后再提交一个块即可,这样做与通过notify函数等待dispatch group执行完毕后再回调块是等效的:

dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivec.queue", NULL);

NSArray *collection;
for (id object in collection) {
    dispatch_async(queue, ^{
        [object description];
    });
}

dispatch_async(queue, ^{
    //Continue processing after completing tasks
});

笔者为何要在标题中谈到“根据系统资源状况来执行任务”呢?回头看看向并发队列派发任务的那个例子,就会明白了。为了执行队列中的块,GCD会在适当的时机自动创建新线程或或复用旧线程。如果并发队列,那么其中有可能会有多个线程,这也就意味着多个块可以并发执行。在并发队列中,执行任务所用的并发线程数量,取决于各个因素,而GCD主要是根据系统资源状况来判定这些因素的。加入CPU有多个核心,并且队列中有大量任务等待执行,那那么GCD就可能会给该队列配备多个线程。通过dispatch group所提供的这种简便方式,既可以并发执行一系列给定的任务,又能在全部任务结束时得到通知。由于GCD有并发队列机制,所以能够根据可用的系统资源状况来并发执行任务。

在前面的范例代码中,我们遍历某个collection,并在其每个元素上执行任务,而这也可以用另外一个GCD函数来实现:

void dispatch_apply(size_t iterations, dispatch_queue_t queue,void (^block)(size_t));

此函数将块反复执行一定的次数,每次传给块的参数值都会递增,从0开始,直至“iterations-1”。其用法如下:

dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivec.queue", NULL);
dispatch_apply(10, queue, ^(size_t i) {
    //Perform task
});

有一件事要注意:dispatch_apply所用的队列可以是并发队列。如果采用并发队列,那么系统就可以根据资源状况来并行执行这些块了,这与使用dispatch group的那段范例代码一样。

这再次表明,未必总要使用dispatch group。然而,dispatch_apply会持续阻塞,直到所有任务都执行完毕为止。由此可见,假如把块派给了当前队列(或者体系中高于当前队列的某个串行队列),就将导致死锁。若想在后台执行任务,则应使用dispatch group。

要点

  • 一系列任务可归入一个dispatch group之中。开发者可以在这组任务执行完毕时获得通知。
  • 通过dispatch group,可以在并发式派发队列里同时执行多项任务。此时,GCD会根据系统资源状况来调度这些并发执行的任务。开发者若自己来实现此功能,则需编写大量代码。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容