0%

Objective-C之GCD概要

Grand Central Dispatch是异步执行任务的技术之一. 开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中, GCD就能生成必要的线程并执行计划任务. 由于线程管理是作为系统的一部分来实现的, 因此可以统一进行管理, 这样就比以前的线程效率更高.

也就是说, GCD用难以置信的简洁语法, 实现了极为复杂繁琐的多线程编程.

Dispatch Queue

Dispatch Queue是执行任务的等待队列, 按照先进先出(FIFO)的顺序执行任务.

存在两种不同的Dispatch Queue, 一种是需要等待当前任务执行完毕的Serial Dispatch Queue, 另一种是不需要等待当前任务执行完毕Concurrent Dispatch Queue.

有两种方法可以获取Dispatch Queue.

dispatch_queue_creat

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

下面的代码创建了一个Serial Dispatch Queue:

dispatch_queue_t serialDQ = dispatch_queue_create("com.example.serialDQ", NULL);

dispatch_queue_create函数的第一个参数指定队列名称, 第二个参数为NULL时生成Serial Dispatch Queue, 若要生成Concurrent Dispatch Queue, 可以将第二个参数指定为DISPATCH_QUEUE_CONCURRENT.

使用dispatch_queue_create函数可以创建任意多个Dispatch Queue. 但不建议大量创建.

需要注意的是, ARC不负责管理生成的Dispatch Queue对象. 通过dispatch_queue_creat生成的Dispatch Queue在使用结束后必须通过dispatch_release函数释放.

dispatch_release(serialDQ);

同样, 还有dispatch_retain函数. 在通过函数或方法获取Dispatch Queue以及其他名称中含有creat的API生成对象时, 有必要通过dispatch_retain函数持有, 并在不需要时通过dispatch_release函数释放.

Main/Global Dispatch Queue

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

Main Dispatch Queue是在主线程中执行的Dispatch Queue. 因为主线程只有一个, 所以是Serial Dispatch Queue.

Global Dispatch Queue是所有应用程序都能使用的Concurrent Dispatch Queue.

Global Dispatch Queue分为4个执行优先级, 分别是高优先级(High Priority), 默认优先级(Default Priority), 低优先级(Low Priority)和后台优先级(Background Priority).

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

另外, 对Main/Global Dispatch Queue使用 dispatch_retain和dispatch_release不会引起任何变化, 也不会有任何问题.

dispatch_set_target_queue

dispatch_queue_creat函数生成的Dispatch Queue都为默认优先级. 要变更生成的Dispatch Queue的优先级可以使用dispatch_set_target_queue函数.

dispatch_queue_t serialDQ = dispatch_queue_create("com.example.serialDQ", NULL);

dispatch_queue_t globalDQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

dispatch_set_target_queue(serialDQ, globalDQ);

dispatch_set_target_queue函数的第一个参数是要变更的queue, 第二个参数是具有要变更到的优先级的queue. 不能使用Main/Global Dispatch Queue作为第一个参数.

dispatch_set_target_queue函数还可以变更Dispatch Queue的执行优先级.

比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:

dispatch_set_target_queue(dispatchA, dispatchB);

那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:

dispatch_suspend(dispatchA);

则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行.

dispatch_after

dispatch_after函数可以再指定时间间隔后执行任务.

实际上, dispatch_after函数只是在指定时间将任务追加到Dispatch Queue. 与在指定时间后使用dispatch_async函数追加block到Main Dispatch Queue相同.

因为Main Dispatch Queue在主线程的RunLoop中执行, 由于RunLoop存在执行周期, 所以dispatch_after函数的延迟执行并不十分精确.

Dispatch Group

如果要在多个任务后完成后执行最终的结束处理, 使用Serial Dispatch Queue时, 可以把结束处理追加到Queue末尾. 但在使用Concurrent 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, ^{
    NSLog(@"blk0");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"blk1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"blk2");
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"done");
});

dispatch_release(group);

执行结果blk0,blk1,blk2打印顺序不一定, 但最后打印的肯定是done.

dispatch_group_sync函数与dispatch_async函数相同, 都追加block到指定的Dispatch Queue中. 与dispatch_async函数不同的是指定的block属于指定的Dispatch Group.

另外, 与追加block到Dispatch Queue相同, block通过dispatch_retain函数持有Dispatch Group, 从而是该block属于Dispatch Group. 当block执行结束后, 再通过dispatch_release函数释放所持有的Dispatch Group. 一旦Dispatch Group使用结束, 不用考虑属于该Dispatch Group的block, 立即通过dispat_release函数释放即可.

在追加到Dispatch Group中的任务全部执行完毕后, dispatch_group_notify函数会将参数中的block追加到Dispatch Group中.

另外, 在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, ^{
    
    sleep(2);
    NSLog(@"blk0");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"blk1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"blk2");
});

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

long result = dispatch_group_wait(group, time);

if (result == 0) {
    NSLog(@"全部执行完");
}else{
    NSLog(@"没有全部执行完");
}

dispatch_release(group);

如果dispatch_group_wait的返回值不为0, 说明等待了指定的时间后, Dispatch Group中的任务还没有全部执行完.

这里的”等待”意味着, 一旦调用dispatch_group_wait函数, 该函数就处于调用的状态而不立即返回. 即执行该函数的进程停止, 直到等待了该函数指定的时间, 所在线程才会继续执行.

第二个参数表示要等待的时间, 指定为DISPATCH_TIME_FOREVER时,意味着永久等待, 直到group中的任务都执行完毕, 中途不能取消; 当指定为DISPATCH_TIME_NOW时, 则表示不用任何等待立即判定group中的任务执行状态.

dispatch_barrier_async

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

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

相应的也有dispatch_barrier_sync函数, 两者的区别是:

在将任务插入到queue的时候,dispatch_barrier_sync需要等待自己的任务结束之后才会继续程序,执行在它之后的任务,而dispatch_barrier_async将自己的任务插入到queue之后,不会等待自己的任务结束,而是继续执行之后的任务.

dispatch_sync

dispatch_async表明将指定的bloc”非同步”地追加到指定的Dispatch Queue中. dispatch_async函数不做任何等待. 而对应的dispatch_sync函数意味着”同步”, 即在追加的block执行完成之前, 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(@"done");

因为是在Concurrent Dispatch Queue中处理, 所以各个打印任务的执行顺序不确定, 但是”done”必定在在最后打印.

dispatch_apply函数的第一个参数是任务重复的次数, 第二个参数是任务执行的Dispatch Queue, 第三个参数是任务的block. 例如:

NSArray *array = @[@"a",@"b",@"c",@"d"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(array.count, queue, ^(size_t index) {
    NSLog(@"%@", array[index]);
});

dispatch_suspend/dispatch_resume

dispatch_suspend函数可以用来挂起Dispatch Queue. 当可以执行时再通过dispatch_resume函数恢复.

这两个函数对已经执行的任务没有影响. 挂起后, Dispatch Queue中未执行的任务会停止执行, 恢复后任务继续执行.

Dispatch Semaphore

考虑一种情况: 不考虑顺序, 将所有数据添加到NSMutableArray中:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [NSMutableArray array];

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);
//参数表示计数的初始化值

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait函数等待Dispatch semaphore的计数大于1或等于1, 在计数大于1或等于1后, 对该计数进行减法并返回. 第二个参数是由dispatch_time_t类型值指定等待时间. 上面代码表示永久等待.

dispatch_semaphore_wait函数返回0时, 可安全地执行需要处理结束时通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1.

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//保证同时可访问的NSMutableArray对象的线程只有1个
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);


NSMutableArray *array = [NSMutableArray array];

for (int i = 0; i < 10000; ++i) {
    dispatch_async(queue, ^{
        //大于等于1, 则将semaphore计数减一
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [array addObject:[NSNumber numberWithInt:i]];
        //排他处理结束, 将semaphore计数加一
        dispatch_semaphore_signal(semaphore);
    });
}
    
dispatch_release(semaphore);

dispatch_once

dispatch_once函数保证应用程序执行中只执行一次指定任务.

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    //处理代码只执行一次, 线程安全
});

Dispatch I/O

在读取较大文件时, 如果将文件切分为合适大小并使用Global Dispatch Queue并行读取的话, 可以加快读取速度. 通过Dispatch I/O和Dispatch Data就能实现这一功能.

通过Dispatch I/O读写文件时,使用Global Dispatch Queue将一个文件按大小read/write, 将文件分割为一块一块地进行读取处理。分割读取的数据通过使用Dispatch Data可更为简单地进行结合和分割。

dispatch_async(queue,^{/*读取  0~8191字节*/});
dispatch_async(queue,^{/*读取  8191~16383字节*/});
dispatch_async(queue,^{/*读取 16384~24575字节*/});
dispatch_async(queue,^{/*读取 24576~32767字节*/});
dispatch_async(queue,^{/*读取 32768~40959字节*/});
dispatch_async(queue,^{/*读取 40960~49151字节*/});
dispatch_async(queue,^{/*读取 49152~57343字节*/});
dispatch_async(queue,^{/*读取 57344~65535字节*/});

以下为苹果中使用Dispatch I/O和Dispatch Data的例子:

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[i];

dispatch_io_set_low_water(pipe_channel,SIZE_MAX);

dispatch_io_read(pipe_channel,0,SIZE_MAX,pipe_q, ^(bool done,dispatch_data_t pipe data,int err){
   if(err == 0)
     {
       size_t len = dispatch_data_get_size(pipe data);
       if(len > 0)
       {
          const char *bytes = NULL;
          char *encoded;

          dispatch_data_t md = dispatch_data_create_map(pipe data,(const void **)&bytes,&len);
          asl_set((aslmsg)merged_msg,ASL_KEY_AUX_DATA,encoded);
          free(encoded);
          _asl_send_message(NULL,merged_msg,-1,NULL);
          asl_msg_release(merged_msg);
          dispatch_release(md);
       }
      }

      if(done)
      {
         dispatch_semaphore_signal(sem);
         dispatch_release(pipe_channel);
         dispatch_release(pipe_q);
      }
});

以上摘自Apple System Log API用的源代码.

dispatch_io_create函数生成Dispatch I/O,并指定发生错误时用来执行处理的Block,以及执行该Block的Dispatch Queue.

dispatch_io_set_low_water函数设定一次读取的大小(分割大小).

dispatch_io_read函数使用Global Dispatch Queue开始并列读取。每当各个分割的文件快读取结束时,将含有文件块数据的Dispatch Data传递给dispatch_io_read函数指定的读取结束时回调用的Block。回调用的Block分析传递过来的Dispatch Data并进行结合处理.

如果想提高文件读取速度,可以尝试使用Dispatch I/O.