zoukankan      html  css  js  c++  java
  • 通过Dispatch Group机制,根据系统资源状况来执行任务

    dispatch group是GCD的一项特性,能够把任务分组,调用者可以等待这组任务执行完毕,也可以在提供回调函数之后继续往下执行,这组任务完成时,调用者会得到通知。

    这个功能有多个用途,其中最重要,最值得注意的用法,就是把将要并发执行的多个任务合为一个组,于是调用者就可以知道这些任务合适才能全部执行完毕。

    举个栗子:把一系列压缩文件的任务表示成dispatch group。

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

      dispatch_group_tdispatch_group_create();

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

    void dispatch_group_async(dispatch_group_t group,

                  dispatch_queue_t queue,

                  dispatch_block_t block);

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

    void dispatch_group_enter(dispatch_group_t group)

    void dispatch_group_leave(dispatch_group_t group)

    前者能够使分组里正要执行的任务数递增,而后者则使之递减。由此可知,调用了dispatch_group_enter以后,必须有与之对应的dispatch_group_leave才行。这与引用计数相似,要使用引用计数,就必须令保留操作与释放操作彼此对应以防内存泄露。而在使用dispatch group时,如果调用enter之后,没有相应的leave操作,那么这一组任务就永远执行不完。

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

      long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

    此函数接受两个参数,一个是要等待的group,另一个是代表等待的时间timeout值。该参数表示函数在等待dispatch group执行完毕是,应该阻塞多久。如果执行dispatch group所需的时间小于timeout,则返回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();
    for (id object in collection){
        dispatch_group_async(dispatchGroup,queue,^{
            [object performTask];
        );      
    }                            
    diapatch_group_wait(dispatchGroup,DISPATCH_TIME_FOREVER);

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

    dispatch_queue_t notifyQueue = dispatch_get_main_queue();
    
    dispatch_group_notify(dispatchGroup,notifyQueue,^{
      //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();
    
    for(id object in lowPriorityObjects){
    
      dispatch_group_async(dispatchGroup,lowPriorityQueue,^{
    
        [object performTask];
    
      }
    
    } 
    
    for(id object in highPriorityObjects){
    
      dispatch_group_async(dispatchGroup,highPriorityQueue,^{
    
        [object performTask];
    
      }
    
    } 
    
    dispatch_queue_t notifyQueue = dispatch_get_main_queue();
    
    dispatch_group_notify(dispatchGroup,notifyQueue,^{
    
      //continue processing after completing tasks  
    
    });

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

    dispatch_queue_t queue = dispatch_queue_create("com.effectiveobjectivc.queue",NULL);
    
    for(id object in collection){
    
      dispatch_async(queue,^{
    
        [object perfomTask];
      });
    
    }
    
    dispatch_async(queue,^{
    
    //continue processing after completing tasks
      }
    
    );

    上面这段代码表明,开发者未必总需要使用dispatch group,有时候采用单个队列搭配标准的异步派发,也可实现同样效果。

    笔者为何要在标题中谈到“根据系统资源状况来执行任务”呢?回头看看向并发队列派发任务的那个例子,就明白了。为了执行队列中的块,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

      }

    )

    采用简单的for 循环,从0递增至9,也能实现同样的效果:

    for(int i = 0;i<10;i++){//perform task}

    有一件事需要注意:dispatch_apply所用的队列可以是并发队列,如果采用并发队列,那么系统可以根据资源状况来并行执行这些块了,这与使用dispatch group的那段范例代码一样。上面这个for循环要处理的collection若是数组,则可用dispatch_apply改写如下:

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

    dispatch_queue_apply(array.coun,queue,^(size_t i){

        id object = array[i];

        [object performTask];  

      };

    );

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

    要点:

    • 一系列任务可归入一个dispatch group中,开发者可以在这组任务执行完毕时获得通知
    • 通过dispatch group,可以在并发式派发队列里同时执行多项任务。此时GCD会根据系统资源状况来调度这些并发执行的任务。开发者若是自己来实现此功能,则需编写大量代码

    摘自 《编写高质量iOS与OS X代码的52个有效方法》 第四十四条。

  • 相关阅读:
    Write an algorithm such that if an element in an MxN matrix is 0, its entire row and column is set to 0.
    旋转二维数组
    replace empty char with new string,unsafe method和native implementation的性能比较
    判断一字符串是否可以另一字符串重新排列而成
    移除重复字符的几个算法简单比较
    也来纠结一下字符串翻转
    判断重复字符存在:更有意义一点
    程序员常去网站汇总
    sublime
    针对程序集 'SqlServerTime' 的 ALTER ASSEMBLY 失败,因为程序集 'SqlServerTime' 未获授权(PERMISSION_SET = EXTERNAL_ACCESS)
  • 原文地址:https://www.cnblogs.com/lingzhiguiji/p/4009346.html
Copyright © 2011-2022 走看看