Grand Central Dispatch是异步执行任务的技术之一. 开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中, GCD就能生成必要的线程并执行计划任务. 由于线程管理是作为系统的一部分来实现的, 因此可以统一进行管理, 这样就比以前的线程效率更高.
也就是说, GCD用难以置信的简洁语法, 实现了极为复杂繁琐的多线程编程.
Dispatch Queue是执行任务的等待队列, 按照先进先出(FIFO)的顺序执行任务.
存在两种不同的Dispatch Queue, 一种是需要等待当前任务执行完毕的Serial Dispatch Queue, 另一种是不需要等待当前任务执行完毕Concurrent Dispatch Queue.
有两种方法可以获取Dispatch Queue.
第一种方法是通过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函数释放.
第二种方法是获取系统提供的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_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 Queue. 与在指定时间后使用dispatch_async函数追加block到Main Dispatch Queue相同.
因为Main Dispatch Queue在主线程的RunLoop中执行, 由于RunLoop存在执行周期, 所以dispatch_after函数的延迟执行并不十分精确.
如果要在多个任务后完成后执行最终的结束处理, 使用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_async函数与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函数会等到追加到Concurrent Dispatch Queue上的并行处理全部结束之后, 再将指定的处理追加到该Concurrent Dispatch Queue中. 然后在等dispatch_barrier_async函数追加的处理执行完成后, Concurrent Dispatch Queue才恢复为一般动作, 开始执行之后追加的并行处理.
使用Concurrent Dispatch Queue和dispatch_barrier_async可以实现高效率的数据库访问和文件访问.
相应的也有dispatch_barrier_async函数, 两者的区别是:
在将任务插入到queue的时候,dispatch_barrier_sync需要等待自己的任务结束之后才会继续程序,执行在它之后的任务,而dispatch_barrier_async将自己的任务插入到queue之后,不会等待自己的任务结束,而是继续执行之后的任务.
dispatch_async表明将指定的bloc”非同步”地追加到指定的Dispatch Queue中. dispatch_async函数不做任何等待. 而对应的dispatch_sync函数意味着”同步”, 即在追加的block执行完成之前, dispatch_sync函数会一直等待(线程阻塞, 等待任务完成).
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 Queue. 当可以执行时再通过dispatch_resume函数恢复.
这两个函数对已经执行的任务没有影响. 挂起后, Dispatch Queue中未执行的任务会停止执行, 恢复后任务继续执行.
考虑一种情况: 不考虑顺序, 将所有数据添加到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); }); } //array.count 不一定是10000 dispatch_release(semaphore);
dispatch_once函数保证应用程序执行中只执行一次指定任务.
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //处理代码只执行一次, 线程安全 });
在读取较大文件时, 如果将文件切分为合适大小并使用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.