zoukankan      html  css  js  c++  java
  • iOS多线程编程之GCD的使用

      什么是线程呢?

      1个CPU执行的CPU命令列为一条无分叉的路径即为线程。

      这种无分叉路径不止1条,存在多条时即为多线程。

      什么是GCD?

      Grand Central Dispatch (GCD)是异步执行任务的技术之一。一般将应用程序中计述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。

        dispatch_async(queue, ^{
            /*
             *长时间处理
             *例如访问数据库
             */
            
            /*
             *长时间处理结束,主线程使用该处理结果
             */
            dispatch_async(dispatch_get_main_queue(), ^{    
                /*
                 *只有主线程可以执行的处理例如用户界面的更新
                 */
            });        
        });

      上面的就是在后台线程中执行长时间处理,处理结束时,主线程使用该处理结果的源代码。

      开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。

      Dispatch Queue是什么呢?如其名称所示,是执行处理的等待队列。开发者通过dispatch_async函数等API,在Block语法中记述想执行的处理并将其追加到等待队列中。Dispatch Queue 按照追加的顺序(先进先出) 执行处理。

      Dispatch Queue 有两种,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue。

    比较这两种Dispatch Queue。准备一下源代码,在dispatch_async中追加多个处理。

        dispatch_async(queue,blk0); 
       dispatch_async(queue,blk1); dispatch_async(queue,blk2); dispatch_async(queue,blk3); dispatch_async(queue,blk4); dispatch_async(queue,blk5);
       dispatch_async(queue,blk6);

      当变量queue为Serial Dispatch Queue时,因为要等待现在执行中的处理结束,所以首先执行bkl0,blk0执行结束后,接着执行blk1,blk1执行结束后再执行blk2,如此重复。同时执行的处理数只能有1个。即执行该源代码后,一定按照一下顺序进行处理。

        blk0
        blk1
        blk2
        blk3
        blk4
        blk5
       blk6

      当变量queue为Concurrent Dispatch Queue 时,因为不用等待现在执行中的处理结束,所以首先执行blk0,不管blk0是否结束,都开始执行后面的blk1,不管blk1是否执行结束,都开始执行后面的blk2,如此重复循环。

      这样虽然不用等待处理结束,可以并行执行多个处理,但并行执行的处理数量取决于当前系统的状态。即iOS和OSX基于Dispatch Queue中的处理数、CPU核数、CPU负荷等当前系统的状态来决定Concurrent Dispatch Queue 中并行执行的处理数,所谓"并行执行",就是使用多个线程同时执行多个处理。

      iOS和OSX的核心一一XNU内核决定应当使用的线程数,并只生成所需的线程执行处理。另外,当执行结束,应当执行的处理减少时,XNU内核会结束不再需要的线程。XNU内核仅使用Concurrent Dispatch Queue 便可完美的管理并执行多个处理的线程。

      假设准备4个Concurrent Dispatch Queue 用线程。首先blk0在线程0中开始执行,接着blk1在线程1中开始执行,blk2在线程2中开始执行,blk3在线程3中开始执行。线程0中blk0执行结束后开始执行blk4,由于线程1中blk1并没有执行结束,因此线程2中blk2执行结束后开始执行blk5,就这样循环往复。

      像这样在Concurrent Dispatch Queue中执行处理时,执行顺序会根据处理内容和系统状态发生改变。它不同于执行顺序固定的Serial Dispatch Queue。在不能改变执行的处理顺序或不想并行执行多个处理时使用Serial Dispatch Queue。

      虽然知道了Serial Dispatch Queue和Concurrent Dispatch Queue,但是如何才能得到这些Dispatch Queue呢,方法有两种。

       dispatch_queue_create

      第一种方法是通过GCD的API去生成Dispatch Queue

      通过 dispatch_queue_create函数可生成Dispatch Queue

        //创建一个串行队列
        dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("serialQueue", NULL);
        //创建一个并行队列
        dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

      dispatch_queue_create函数的第一个参数指定Dispatch Queue的名称。第二个参数指定为Dispatch Queue的类型。生成Serial Dispatch Queue时默认为NULL,生成Concurrent Dispatch Queue时指定为DISPATCH_QUEUE_CONCURRENT。

      dispatch_queue_create函数的返回值为表示Dispatch Queue的"dispatch_queue_t类型"。在之前的源代码中所出现的变量queue均为dispatch_queue_t类型变量。

      需要指出的是当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然在1个Serial Dispatch Queue中同时只能执行一个追加处理,但如果将处理分别追加到4个Serial Dispatch Queue中,各个Serial Dispatch Queue各执行1个,即为同时执行4个处理。

      一旦生成Serial Dispatch Queue并追加处理,系统对于Serial Dispatch Queue就只生成并使用一个线程。如果生成2000个Serial Dispatch Queue,那么就生成2000个线程。如果多使用线程,就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应时间。

      因此,只有当多个线程更新相同资源导致数据竞争时使用Serial Dispatch Queue,当想并行执行不发生数据竞争等的问题的处理时,使用Concurrent Dispatch Queue。

      Main Dispatch Queue/Global Dispatch Queue

      第二种方法是获取系统标准提供的Dispatch Queue

      实际上不用特意生成Dispatch Queue系统也会给我们提供几个。那就是Main Dispatch Queue 和 Global Dispatch Queue。

       Main Dispatch Queue正如其名称中含有的"Main"一样,是在主线程中执行的Dispatch Queue。因为主线程只有一个,所以Main Dispatch Queue自然是Serial Dispatch Queue。追加到Main Dispatch Queue的处理在主线程的RunLoop中执行。由于在主线程中执行,因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue使用。

      另一个Global Dispatch Queue是所有的应用程序都能够使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue。只要获取Global Dispatch Queue即可。

      另外Global Dispatch Queue有4个执行优先级,分别是高优先级(High Priority)、默认优先级(Default Priority)、低优先级(Low Priority)和后台优先级(Background Priority)。但是执行优先级只是大致的判断。例如在处理内容的执行可有可无时,使用后台优先级等,只能进行这种程度的区分。

      各种 Dispatch Queue 的获取方法如下

        //获取主线程
        dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
        //高优先级
        dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
        //默认优先级
        dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //低优先级
        dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW , 0);
        //后台优先级
        dispatch_queue_t globalDispatchQueueBackgroud = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND , 0);

      dispa_set_target_queue

      dispatch_queue_create函数生成的Dispatch Queue 不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。而变更生成的Dispatch Queue 的优先级要使用dispatch_set_target_queue函数。在后台执行动作处理的Serial Dispatch Queue的生成方法如下:

            dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("serialQueue", NULL);
            dispatch_queue_t globalDispatchQueueBackgroud = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND , 0);    
            dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackgroud);

      指定变更优先级的Dispatch Queuedispatch_set_target_queue函数的第一个参数,指定与要使用的执行优先级的Global Dispatch Queue为第二个参数(目标)。第一个参数不能指定系统提供的Main Dispatch Queue和Global Dispatch Queue。

      将Dispatch Queue指定为dispatch_set_target_queue函数的参数,不仅可以变更Dispatch Queue的执行优先级,还可以作成Dispatch Queue的执行阶层。如果在多个Serial Dispatch Queue中用dispatch_set_target_queue函数指定目标为某一个Serial Dispatch Queue,那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理。

      在必须将不可并行执行的处理追加到多个Serial Dispatch Queue中时,如果使用dispatch_set_target_queue函数将目标指定为某一个Serial Dispatch Queue,即可放置处理并行执行。

       dispatch_after

      经常会有这样的情况:想在3秒后执行处理。可能不仅限于3秒,总之,这种想在指定事件后执行处理的情况,可使用dispatch_after函数来实现。

      在3秒后将指定的Block追加到Main Dispatch Queue 中的源代码如下:  

       dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,3ull*NSEC_PER_SEC);
        dispatch_after(time, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"3秒后");
        });
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
             NSLog(@"3秒后");
        });

      需要注意的是,dispatch_after函数并不是在指定的时间后执行处理,而只是在指定的时间追加处理到Dispatch Queue。此源代码在3秒后用dispatch_async函数追加Block到Main Dispatch Queue的相同。

      因为Main Dispatch Queue在主线程的RunLoop中执行,所以再比如每隔1/60秒执行的RunLoop中,Block最快在3秒后执行,最慢在3秒+1/60秒后执行,并且在Main Dispatch Queue有大量处理追加或主线程的处理本身有延迟时,这个事件会更长。

      虽然在有严格时间的要求下使用时会出现问题,但在想大致延迟执行处理时,该函数是非常有效的。

      另外第二个参数指定要追加处理的Dispatch Queue,第三个参数指定记述要执行处理的Block。

      第一个参数时指定时间用的dispatch_time_t类型的值。该值使用dispatch_time函数或dispatch_walltime函数作成。

      dispatch_time函数能够获取从第一个参数dispatch_time_t类型值中指定的时间开始,到第二个参数指定的毫微秒单位时间后的时间。第一个参数经常使用的值是之前源代码中出现的DISPATCH_TIME_NOW。这表示现在的时间。即以下源代码可得到表示从现在开始1秒后的dispatch_time_t的值。

        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);

      数值和NSEC_PER_SEC的乘积得到单位为毫秒的数值,"ull"是C语言的数值字面量是显式表示类型时使用的字符串(表示"unsigned long long")。如果使用NSEC_PER_MSEC则可以以毫秒为单位计算。一下源代码为从现在开始150毫秒后时间的值。

        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 150ull*NSEC_PER_MSEC);

        

      Dispatch Group

      在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这种情况会经常出现。只使用一个Serial Dispatch Queue时,只要将项执行的处理全部追加到该 Serial Dispatch Queue中并在最后追加结束处理,即可实现。但是在使用Concurrent Dispatch Queue时或同时使用多个Dispatch Queue时,源代码就会变得颇为复杂。

      在这种情况下使用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(@"blk0");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"blk1");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"blk2");
        });
        dispatch_group_notify(group, queue, ^{
            NSLog(@"结束了");
        });

      该代码执行结果如下

        blk1
        blk2
        blk0
        结束了

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

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

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

      另外,在Dispatch Queue中也可以使用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(@"blk0");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"blk1");
        });
        dispatch_group_async(group, queue, ^{
            NSLog(@"blk2");
        });
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

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

      如同dispatch_after函数说明的那样,指定等待间隔为1秒时应作出以下处理。

       dispatch_time_t time2 = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
        
        long result = dispatch_group_wait(group, time2);
    
        if (result == 0) {
            /*
             *属于Dispatch Group的全部处理结束执行
             */
        }else{
            /*
             *属于Dispatch Group的某一个处理还在执行中
             */
        }

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

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

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

     long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);

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

      dispatch_barrier_async

      在访问数据库或文件时,如前所述,使用Serial Dispatch Queue可避免数据竞争的问题。

      写入处理确实不可以与其他的写入处理以及包含读取处理的其他某些处理并行执行。但是如果读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。

      也就是说,为了高效率的进行访问,读取处理追加到Concurrent Dispatch Queue中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在写入处理结束之前,读取处理不可执行)。

      虽然利用Dispatch Group和dispatch_set_target_queue函数也可实现,但是源代码会很复杂。

      GCD为我们提供了更为聪明的解决方法一一dispatch_barrier_async函数。该函数同dispatch_queue_create函数生成的Concurrent Dispatch Queue 一起使用。

      首先dispatch_queue_create函数生成Concurrent Dispatch Queue,在dispatch_async下做追加处理。

        dispatch_queue_t queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        
       dispatch_async(queue, blk0_for_reading); dispatch_async(queue, blk1_for_reading); dispatch_async(queue, blk2_for_reading); dispatch_async(queue, blk3_for_reading); dispatch_async(queue, blk4_for_reading); dispatch_async(queue, blk5_for_reading); dispatch_async(queue, blk6_for_reading); dispatch_async(queue, blk7_for_reading);

      在 blk3_for_reading blk4_for_reading处理之间执行写入处理,并将写入的内容读取 blk4_for_reading 处理以及之后的处理中。

        dispatch_async(queue, blk0_for_reading);
        dispatch_async(queue, blk1_for_reading);
        dispatch_async(queue, blk2_for_reading);
        dispatch_async(queue, blk3_for_reading);
      /*
      *写入处理
      *将写入的内容读取之后的处理中
      */
        dispatch_async(queue, blk4_for_reading);
        dispatch_async(queue, blk5_for_reading);
        dispatch_async(queue, blk6_for_reading);
        dispatch_async(queue, blk7_for_reading);

      如果向下面这样简单的在dispatch_async函数中加入写入处理,那么根据Concurrent Dispatch Queue的性质,就有可能在追加到写入处理前面的处理中读取到与期待不符的数据,还可能因非法访问导致应用程序异常结束。如果追加多个写入处理,则可能发生更多问题,比如数据竞争。

        dispatch_async(queue, blk0_for_reading);
        dispatch_async(queue, blk1_for_reading);
        dispatch_async(queue, blk2_for_reading);
        dispatch_async(queue, blk3_for_reading);
    dispatch_async(queue, blk_for_writing);
    dispatch_async(queue, blk4_for_reading); dispatch_async(queue, blk5_for_reading); dispatch_async(queue, blk6_for_reading); dispatch_async(queue, blk7_for_reading)

      因此我们要使用dispatch_barrier_async函数。dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中。然后由 dispatch_barrier_async(globalDispatchQueueDefault, ^{
            NSLog(@"writing");
        });追加的处理执行完毕后,Concurrent Dispatch Queue才恢复为一般的动作,追加到该Concurrent Dispatch Queue的处理又开始并行执行。

        dispatch_async(queue, blk0_for_reading);
        dispatch_async(queue, blk1_for_reading);
        dispatch_async(queue, blk2_for_reading);
        dispatch_async(queue, blk3_for_reading);
    
        dispatch_barrier_async(queue, blk_for_writing});
    
        dispatch_async(queue, blk4_for_reading);
        dispatch_async(queue, blk5_for_reading);
        dispatch_async(queue, blk6_for_reading);
        dispatch_async(queue, blk7_for_reading);

      如上所示,使用方法非常简单。仅使用dispatch_barrier_async函数代替dispatch_async函数即可。

      使用dispatch_barrier_async和Concurrent Dispatch Queue可实现高效率的数据库访问和文件访问。

      dispatch_sync

      dispatch_async函数的"async"意味着"非同步"(asynchronous),就是将指定的Block"非同步"的追加到指定的Dispatch Queue中,dispatch_async函数不作任何等待。

      既然有"async'就有"sync",即dispatch_sync函数。它意味着"同步"(synchronous),也就是将指定的Block"同步"追加到指定的Dispatch Queue中。在追加Block结束之前,dispatch_sync函数会一直等待。即意味着当前线程停止。

       我们假设这样一种情况:执行Main Dispatch Queue时,使用另外的线程Global Dispatch Queue进行处理,处理结束后立即使用所得到的结果。在这种情况下使用dispatch_sync函数。

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_sync(queue,^{
        /*
        *处理
        */
     });

      一旦调用dispatch_sync函数,那么在指定的处理执行结束之前,该函数不会返回。dispatch_sync函数可以简化源代码,也可以说是减一半的dispatch_group_wait函数。

      正因为dispatch_sync函数使用简单,所以也容易出现死锁问题。

      例如在主线程中执行以下源代码就会死锁。

        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            NSLog(@"洛洛爱吃肉");
        });

      该源代码在Main Dispatch Queue 及主线程中执行指定的Block,并等待其执行结束。而其实主线程中正在执行这些源代码,所以无法执行追加到Main Dispatch Queue 的Block。下面例子也一样。

        dispatch_queue_t queue = dispatch_get_main_queue();
       dispatch_async(queue, ^{
         dispatch_sync(queue,^{
              NSLog(@"洛洛爱吃肉");
          }); });

      Main Dispatch Queue中执行的Block等待Main Dispatch Queue中要执行的Block执行结束。这样的死锁就像在画像上画画一样。

      当然 Serial Dispatch Queue 也会引起相同的问题。

        dispatch_queue_t queue = dispatch_queue_create("serialQueue", NULL);
        dispatch_async(queue, ^{
            dispatch_sync(queue, ^{
                NSLog(@"洛洛爱吃肉");
            });
        });

      另外的由dispatch_barrier_async函数中含有async可推测出,相应的也有dispatch_barrier_sync函数。dispatch_barrier_async函数的作用是等待追加的处理全部执行结束后,再追加处理到Dispatch Queue中,此外,它还与dispatch_sync函数相同,会等待追加处理的执行结束。

      

      dispatch_apply

      dispatch_apply函数是dispatch_sync函数与Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。

        dispatch_apply(10, globalDispatchQueueDefault, ^(size_t index) {
            NSLog(@"%zu",index);
        });
        NSLog(@"done");

      例如该源码的执行结果为:

        4
        1
        0   
        3
        2
        6    
        7
        9
        8
        5
        done

      因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定。但是输出结果中最后的done必定在最后的位置上。这是因为dispatch_apply函数会等待全部处理执行结束。

       第一个参数为重复次数,第二个参数为追加对象的Dispatch Queue,第三个参数为追加的处理。与到目前位置出现的例子不同,第三个参数的Block为带有参数的Block。这是为了按第一个参数重复追加Block并区分各个Block而使用。例如对NSArray类对象的所有元素执行处理时,不必一个一个编写for循环部分。

      我们来看一下下面的源代码,变量array为NSArray类对象。

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
      
        dispatch_apply([array count], queue, ^(size_t index) {
            NSLog(@"%zu: %@",index,[array objectAtIndex:index]);
        })

      这样可以简单的在Global Dispatch Queue 中对所有元素执行Block。

      另外,由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        /*
         *在Global Dispatch Queue中非同步执行
         */
        dispatch_async(queue, ^{
            /*
             *  Global Dispatch Queue
             *  等待dispatch_apply函数中全部处理结束
             */
            dispatch_apply([array count], queue, ^(size_t index) {
                /*
                 *并列处理包含在NSArray对象的全部对象
                 */
                NSLog(@"%zu: %@",index,[array objectAtIndex:index]);
            });
            /*
             *dispatch_apply函数中的处理全部执行结束
             */
            
            /*
             * 在Main Dispatch Queue 中非同步执行
             */
            dispatch_async(dispatch_get_main_queue(), ^{
                /*
                 * 在Main Dispatch Queue 中执行处理
                 * 用户界面更新等
                 */
                NSLog(@"done");
            });
        });

      

      dispatch_suspend/dispatch_resume

      当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理。例如演算结果被Block截获时,一些处理会对这个演算结果造成影响。在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再恢复。

      dispatch_suspend函数挂起指定的Dispatch Queue。

        dispatch_suspend(queue);

      dispatch_resume函数恢复指定的Dispatch Queue。

        dispatch_resume(queue);

      这些函数对已经执行的处理没有影响。挂起后,追加到中但尚未执行的处理在此之后停止执行。而恢复则使得这些处理能够继续执行。

     

      Dispatch Semaphore

      如前所述,当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束。虽然使用Serial Dispatch Queue和dispatch_barrier_async函数可避免这类问题,但是有必要进行更细粒度的排他控制。

      我们来思考一下这种情况:不考虑顺序,将所有数据追加到NSMutableArray中。

     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
        NSMutableArray *array = [[NSMutableArray alloc]init];
        
        for (int i = 0; i<10000; i++) {
            dispatch_async(queue, ^{
                [array addObject:[NSNumber numberWithInt:i]];
            });
        }

      因为该源代码使用 Global Dispatch Queue 更新NSMutableArray对象,所以执行后由内存错误导致应用程序异常结束的概率很高。此时应使用Dispatch Semaphore 。

      Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。所谓信号,类似于过马路时常有的手旗。可以通过时举起手旗,不可以通过时放下手旗。而在Dispatch Semaphore中,使用计数来实现该功能。计数为0时等待,计数为1或大于1时,减去1而不等待。

      下面介绍一下使用方法。通过dispatch_semaphore_create函数生成Dispatch Semaphore。 

        dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

      参数表示计数的初始值。本例将计数值初始化为"1"。

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

      dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值大于或等于1。当计数值大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回。第二个参数与dispatch_group_wait函数等相同,由dispatch_time_t类型值指定等待时间。该例的参数意味着永久等待。另外,dispatch_semaphore_wait函数的返回值也与dispatch_group_wait函数相同。可以像以下源代码这样,通过返回值进行分支处理。

        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
        long result = dispatch_semaphore_wait(semaphore, time);
        if (result == 0) {
            /*
             *  由于Dispatch Semaphore 的计数值达到大于等于1
             *  或者在待机的等待时间内
             *  Dispatch Semaphore 的计数值达到大于等于1
             *  所以Dispatch Semaphore 的计数值减去1
             *
             *  可执行需要进行的排他控制的处理
             */
        }else{
            /*
             *  由于Dispatch Semaphore 的计数值为0
             *  因此在到达指定时间为止待机
             */
        }

      dispatch_semaphore_wait函数返回为0时,可安全的执行需要进行排他控制的处理。该处理结束时通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1。

      我们在前面的源代码中使用Dispatch Semaphore看看。

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     /*
         *  生成Dispatch Semaphore 
         *  Dispatch Semaphore的计数初始值设定为"1"
         *
         *  保证可访问NSMutableArray类对象的线程
         *  同时只能有一个
         */
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
        NSMutableArray *array = [[NSMutableArray alloc]init];
        
        for (int i = 0; i<10000; i++) {
            dispatch_async(queue, ^{
                
                /*
                 *  等待Dispatch Semaphore
                 *
                 *  一直等待,直到Dispatch Semaphore的计数值达到大于等于1。
                 */
                
                dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                
                /*
                 *  由于Dispatch Semaphore的计数值达到大于等于1
                 *  所以将Dispatch Semaphore的计数值减去1
                 *  dispatch_semaphore_wait函数执行返回
                 *
                 *  即执行到此时的
                 *  Dispatch Semaphore的计数值恒为"0"
                 *
                 *  由于可访问的 NSMutableArray类对象的线程
                 *  只有1个
                 *  因此可安全的进行更新
                 */
                
                [array addObject:[NSNumber numberWithInt:i]];
                
                /*
                 *  排他控制处理结束
                 *  所以通过dispatch_semaphore_signal函数
                 *  将Dispatch Semaphore的计数值加1
                 *
                 *  如果有通过dispatch_semaphore_wait函数
                 *  等待Dispatch Semaphore 的计数值增加的线程
                 *  就由最先等待的线程执行
                 */
                dispatch_semaphore_signal(semaphore);
            });
        }

      在没有 Serial Dispatch Queue和dispatch_barrier_async函数那么大粒度且一部分处理需要进行排他控制的情况下Dispatch Semaphore便可发挥威力。

      

      dispatch_once

       dispatch_once函数是保证在应用程序中只执行一次指定处理的API。下面这种经常出现的用来进行初始化的源代码可通过dispatch_once函数简化。

       static  int initialized = NO;
        if (initialized == NO) {
            
            /*
             *初始化
             */
            
            initialized = YES;
        }

      如果使用dispatch_once函数,则源代码写为:

       static dispatch_once_t pred;
        dispatch_once (&pred,^{
            
            /*
             *初始化
             */
            
        });

      源代码看起来没有太大的变化。但是通过dispatch_once函数,该原代码即使在多线程环境下执行,也可保证百分比安全。

      之前的源代码在大多数情况下也是安全的。但是在多核CPU中,在正在更新表示是否初始化的标志变量时读取,就有可能多次执行初始化处理。而用dispatch_once函数初始化就不必担心这样的问题。这就是所说的单例模式,在生成单例对象时使用。

     查看Demo

    作者:洛洛爱吃肉 出处:http://www.cnblogs.com/dongliu/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。
  • 相关阅读:
    EasyUI dialog
    winform 收集
    ASP.NET文件下载,直接向客户端输出文件(转)
    反射收集
    Sql Server 2008 R2 备份数据库报错
    慎用Ext.QuickTip和Ext.QuickTips(转)
    使用正则Regex来移除网页的ViewState(转)
    mysql 实现远程链接(转)
    MySqlBackup.NET,C# mysql备份数据库
    Firefox下站长统计代码不起作用
  • 原文地址:https://www.cnblogs.com/dongliu/p/5390914.html
Copyright © 2011-2022 走看看