GCD的API详解

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。

GCD的API详解_第1张图片
避免数据竞争

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执行过程

GCD的API详解_第2张图片
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详解)