GCD的API详解

作者: 面试小集 | 来源:发表于2016-01-09 15:33 被阅读685次

    Dispatch Queue

    首先回顾一下苹果GCD的说明

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

    这句话的用源代码表示如下:

    dispatch_asyn(queue, ^{
      //想要执行的任务
    });
    

    该源代码使用block语法定义想要执行的任务,通过dispatch_async函数追加赋值变量queue的“Dispatch Queue”中,仅这样就可以使指定的block在另一线程中执行。

    Dispatch Queue 是什么,是执行处理的等待队列,应用程序编程人员通过dispatch_any等函数,在block中记述想要执行等处理并将其追加到Dispatch Queue中,Dispatch Queue按照追加的顺序执行处理。

    两种Dispatch Queue

    Dispatch Queue种类 说明
    Serial Dispatch Queue 等待现在执行中处理结束
    Concurrent Dispatch Queue 不等待现在执行中处理

    Serial Dispatch Queue: 假设把A B C D四个任务依次加入到一个Serial Dispatch Queue中,这个Serial Dispatch Queue会创建一个线程,依次执行这四个任务。

    Concurrent Dispatch Queue: 假设把A B C D四个任务依次加入到一个Concurrent Dispatch Queue中,这个Concurrent Dispatch Queue会创建多个线程,并发执行这四个任务。

    具体执行效果参看这个Demo

    dispatch_queue_create

    通过dispatch_queue_create函数可生成Dispatch Queue。

    创建Serail Queue:

    dispatch_queue_t mySerailQueue = dispatch_queue_create("com.river.dispatchSerailQueue", NULL)
    ;
    

    第一个参数是指定Serial dispatch queue的名称。该名称在Xcode和Instruments的调试器中作为Dispatch Queue名称表示。

    第二个参数是生成Dispatch Queue的类型,如果是NULL生成Serial dispatch queue,指定为DISPATCH_QUEUE_CONCURRENT生成Concurrent Dispatch Queue。

    创建Concurrent Queue:

    dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.river.dispatchConcurrentQueue", DISPATCH_QUEUE_CONCURRENT)
        ;
    

    dispatch_queue_create函数的返回值为表示Dispatch Queue的“dispatch_queue_t”类型。

    注意:Serial Dispatch Queue只能同时执行一个追加处理。使用dispatch_queue_create可以生成多个dispatch queue。当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然一个Serial Dispatch Queue同时只能执行一个追加处理,但是如果将处理(任务)追加到4个Serial Dispatch Queue,各个Serial Dispatch Queue执行1个,即为同时执行4个处理。但是,要警惕的是,如果生成太多的Serial Dispatch Queue会消耗大量内存。为了避免多线程造成的数据竞争,可以使用Serial Dispatch Queue。

    避免数据竞争

    ARC下dispatch_release使用

    在iOS6之前GCD未加入到ARC模式下,所以如果应用程序要支持iOS6.0之前对版本,则需要使用dispatch_release。可以做如下处理。

    //当前系统支持的最小版本
    #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000
        #define NEEDS_DISPATCH_RETAIN_RELEASE 0
    #else                                         // iOS 5.X or earlier
        #define NEEDS_DISPATCH_RETAIN_RELEASE 1
    #endif
    
    /* implementation */
    #if NEEDS_DISPATCH_RETAIN_RELEASE
        dispatch_release(self.reachabilitySerialQueue);
    #endif
    

    Main Dispatch Queue/Global Dispatch Queue

    获取系统标准提供的Dispatch Queue。

    Main Dispatch Queue是在主线程执行的dispatch queue, Main Dispatch Queue是一个Serail 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有四个优先级

    名称 Dispatch Queue的种类 说明
    Main Dispatch Queue Serial Dispatch Queue 主线程执行
    Global Dispatch Queue(High Priority) Concurrent Dispatch queue 执行优先级:高(最高)
    Global Dispatch Queue(Default Priority) Concurrent Dispatch queue 执行优先级:默认
    Global Dispatch Queue(Low Priority) Concurrent Dispatch queue 执行优先级:低
    Global Dispatch Queue(Background Priority) Concurrent Dispatch queue 执行优先级:后台

    获取Dispatch Queue方法

    ispatch_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 globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    

    使用Main Dispatch Queue 和 Global Dispatch Queue

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
            /**
             *  可并行处理的任务TODO
             */
    
            dispatch_async(dispatch_get_main_queue(), ^{
                /**
                 *  主线程执行
                 */
            });
        });
    

    dispatch_set_target_queue

    dispatch_queue_create函数生成的Dispatch_Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。而变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数。

    dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.river.myserialdispatchqueue", NULL);
        dispatch_queue_t globalDispatchQueueBackGround = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
        dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackGround);
    

    第一个参数:要变更执行优先级的Dispatch Queue

    第二个参数:要使用的执行优先级相同的Global Dispatch Queue。

    如果指定第一个参数为Main Dispatch Queue 和 Global Dispatch Queue则不知道会发生什么情况。

    如果在多个Serial Dispatch Queue 中将一个Serial Dispatch Queue设置为第二个参数,那么原本应该并行执行的Serial Dispatch Queue,在目标Serial Dispatch Queue上只能串行执行。可参考Demo

    dispatch_after

    dispatch_after表示在指定的时间之后追加处理到Dispatch Queue。并不是指定时间后执行处理。

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
            NSLog(@"等待3秒后执行");
     });
    

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

    dispatch_time函数通常用于计算相对时间,而dispatch_walltime函数用于计算绝对时间。

    NSDate转成dispatch_time_t

    - (dispatch_time_t) getDispatchDateByDate:(NSDate *)date{
        NSTimeInterval interval;
        double second, subsecond;
        struct timespec time;
        dispatch_time_t milestone;
    
        //以当前时间(Now)为基准时间,返回实例保存的时间与当前时间(Now)的时间间隔,秒
        interval = [date timeIntervalSince1970];
        //拆分interval值,返回它的小数部分,second指向整数部分
        subsecond = modf(interval, &second);
        time.tv_sec = second;   //秒
        time.tv_nsec = subsecond *NSEC_PER_SEC; //纳秒 1秒=1000000000纳秒
        milestone = dispatch_walltime(&time, 0);
        return milestone;
    }
    

    Dispatch Group

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

    在这种情况下使用Dispatch Group。

    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, ^{
            for (int i=0; i<2000; i++) {
                NSLog(@"111111");
            }
        });
        dispatch_group_async(group, queue, ^{
            for (int i=0; i<2000; i++) {
                NSLog(@"2222222");
            }
        });
        dispatch_group_async(group, queue, ^{
            for (int i=0; i<2000; i++) {
                NSLog(@"33333");
            }
        });
        //最后执行4444
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"44444");
        });
        NSLog(@"555555");
    

    另外,在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, ^{
            for (int i=0; i<2000; i++) {
                NSLog(@"111111");
            }
        });
        dispatch_group_async(group, queue, ^{
            for (int i=0; i<2000; i++) {
                NSLog(@"2222222");
            }
        });
        dispatch_group_async(group, queue, ^{
            for (int i=0; i<2000; i++) {
                NSLog(@"33333");
            }
        });
        //DISPATCH_TIME_FOREVER永久等待
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"44444");
    

    dispatch_group_wait函数的第二个参数指定为等待的时间,它属于dispatch_time_t类型的值。

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
        long result = dispatch_group_wait(group, time);
        if (result == 0) {
            /**
             *属于dispatch group的任务全部执行完毕
             */
        }else {
            /**
             *属于dispatch group的任务没有全部执行完毕
             */
        }
    

    dispatch_group_wait等待的意思:一旦调用dispatch_group_wait,该函数就处于调用状态而不返回,执行dispatch_group_wait函数的线程停止。知道dispatch_group_wait返回结果。

    long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
    

    注意推荐使用dispatch_group_notify;

    dispatch_barrier_async

    dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上对并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中,然后在由dispatch_barrier_async函数追加的处理执行完毕之后,Concurrent Dispatch Queue才会恢复一般动作。

     dispatch_async(queue, ^{
            //去文件
        });
        dispatch_async(queue, ^{
            //去文件
        });
        dispatch_async(queue, ^{
            //去文件
        });
        //使用dispatch_barrier_async避免数据竞争
        dispatch_barrier_async(queue, ^{
            //写文件
        });
        dispatch_async(queue, ^{
            //去文件
        });
        dispatch_async(queue, ^{
            //去文件
        });
    
        dispatch_async(queue, ^{
            //去文件
        });
    

    dispatch_barrier_async执行过程

    dispatch_barrier_async图解

    dispatch_sync

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

    dispatch_sync函数意味着同步,也就是将指定的Block同步追加到Dispatch Queue中,再追加结束之前dispatch_sync函数会一直等待。一旦调用dispatch_sync函数,那么指定的处理执行结束之前,该函数不会返回。

    dispatch_sync函数容易引起问题,死锁

    dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
            NSLog(@"11111");
        });
    

    该段代码在主线程中执行指定的Block,并等待其执行结束。而其实主线程正在执行此段代码,所以无法执行追加到Mian Dispatch Queue中的Block。

    dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_async(queue, ^{
            dispatch_sync(queue, ^{
                NSLog(@"11111");
            });
        });
    

    这段代码同样会引起错误。同样下面代码也会引起错误

        dispatch_queue_t queue = dispatch_queue_create("com.river.queue", NULL);
        dispatch_async(queue, ^{
            dispatch_sync(queue, ^{
                NSLog(@"11111");
            });
        })
    

    大家使用dispatch_sync时候要好好考虑。

    dispatch_apply

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

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        dispatch_apply(10, queue, ^(size_t index) {
            NSLog(@"%zu", index);//并行
        });
        //最后执行
        NSLog(@"11");
    

    对数组的快速处理:

    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]);
        });
    

    推荐dispatch_async函数非同步地执行dispatch_apply函数

      dispatch_async(queue, ^{
            dispatch_apply([array count], queue, ^(size_t index) {
                /**
                 *  并行执行
                 */
                NSLog(@"%zu:%@", index, [array objectAtIndex:index]);
            });
    
            /**
             *  等待dispatch_apply全部处理完
             */
    
            dispatch_async(dispatch_get_main_queue(), ^{
                /**
                 *  在main dispatch queue中执行,用户界面更新等
                 */
                NSLog(@"done");
            });
    
        });
    

    dispatch_suspend/dispatch_resume

    当追加大量处理到Dispatch Queue时,在追加处理到过程中,有时希望不执行已追加的处理。在这种情况下只要挂起Dispatch Queue即可。

    dispatch_suspend 函数挂起指定的Dispatch Queue

    dispatch_resume 函数恢复指定的Dispatch Queue

    这些函数对已经执行的处理没有影响,挂起后,追加到Dispatch Queue中处理在此之后暂停执行,而恢复使得这些处理继续执行。

    Dispatch Semaphore

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

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

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    

    参数表示计数的初始值。

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    

    dispatch_semaphore_wait函数等待Dispatch Semaphore 的计数值达到大于或等于1。当计数值大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回。

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
        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_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的计数值恒为00
                 
                 由于可访问NSMutableArray类对象的线程数只有1个
                 因此可安全的进行更新
                 */
                [array addObject:[NSNumber numberWithInt:i]];
                
                /**
                 * 排他控制处理结束
                 所以通过dispatch_semaphore_signal函数
                 将DispatchSemaphore的计数值加1
                 如果有通过dispatch_semaphore_wait函数
                 等待Dispatch Semaphore的计数值增加的线程
                 就由最先等待的线程执行。
                 */
                dispatch_semaphore_signal(semaphore);
            });
        }
    

    dispatch_once

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

    static int initialized = NO;
    if(initialized == NO){
      initialized = YES;
    }
    
    //使用dispatch_once
    static disptach_once_t pred;
    dispatch_once(&pred, ^{
      //初始化
    });
    

    通过dispatch_once函数,该代码再多线程环境下也是百分之百安全。

    Dispatch I/O

    在读取大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue并列读取的话,应该会比一般的读取速度快不少。实现这一功能可使用Dispatch I/O和Dispatch Data。

    通过dispatch I/O读写文件时,使用Global Dispatch Queue将一个文件按某个大笑read/write.

        dispatch_async(queue, ^{
            /**
             *  读取 0~1001字节
             */
        });
        dispatch_async(queue, ^{
            /**
             *  读取 1002~2001字节
             */
        });
        dispatch_async(queue, ^{
            /**
             *  读取 2002~3001字节
             */
        });
        dispatch_async(queue, ^{
            /**
             *  读取 3002~4001字节
             */
        });
    

    demo

     pipe_q = dispatch_queue_create("PipeQ", NULL);
            pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
                close(fd);
            });
            
            *out_fd = fdpair[1];
            
            dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
            
            dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
                if (err == 0)
                {
                    size_t len = dispatch_data_get_size(pipedata);
                    if (len > 0)
                    {
                        const char *bytes = NULL;
                        char *encoded;
                        uint32_t eval;
                        
                        dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
                        encoded = asl_core_encode_buffer(bytes, len);
                        asl_msg_set_key_val(aux, ASL_KEY_AUX_DATA, encoded);
                        free(encoded);
                        eval = _asl_evaluate_send(NULL, (aslmsg)aux, -1);
                        _asl_send_message(NULL, eval, aux, NULL);
                        asl_msg_release(aux);
                        dispatch_release(md);
                    }
                }
              if (done)
                {
                    dispatch_semaphore_signal(sem);
                    dispatch_release(pipe_channel);
                    dispatch_release(pipe_q);
                }
            });
    

    相关文章

      网友评论

        本文标题:GCD的API详解

        本文链接:https://www.haomeiwen.com/subject/muafkttx.html