Grand Central Dispatch是异步执行任务的技术
在GCD之前,Cocoa框架提供处理异步执行任务的API

performSelectorInBackground: withObject: //后台处理任务 performSelectorOnMainThread:withObject: waitUntilDone: //回到主线程处理任务

(1)Dispatch Queue

开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
Dispatch Queue是执行处理的等待队列,按照FIFO的执行处理。
两种不同的执行队列:

Dispatch Queue种类 说明
Serial Dispatch Queue 等待现在执行中处理结束 (串行队列)
Concurrent Dispatch Queue 不等待现在执行中处理结束(并行队列)

(2)dispatch_queue_create

dispatch_queue_create函数用于生成Dispatch Queue

  1. dispatch_queue_t queue = dispatch_queue_create("com.name", DISPATCH_QUEUE_SERIAL);

两个参数:

  • 第一个参数指定Queue的名称。推荐使用逆序全程域名
  • 第二个参数指定Queue的类型,NULL或DISPATCH_QUEUE_SERIAL都是表示Serial Dispatch Queue,指定为DISPATCH_QUEUE_CONCURRENT表示Concurrent Dispatch Queue

dispatch_release函数释放创建的队列,对应的也就有dispatch_retain函数来持有队列。

(3) Main Dispatch Queue / Global Dispatch Queue

Main Dispatch Queue 是Serial Dispatch Queue,因为主线程只有一个。添加到Main Dispatch Queue的处理在主线程的Runloop中执行。
Global Dispatch Queue是Concurrent Dispatch Queue。Global Dispatch Queue有4个执行优先级,分别是高优先级,默认优先级,低优先级,后台优先级。但是通过XNU内核用于Global 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 后台

获取全局队列,第一个参数是优先级,第二个参数作为保留字段,一般为0

  1. // 获取全局队列
  2. dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
  3. // 获取主队列
  4. dispatch_get_main_queue()
参数类型 说明
DISPATCH_QUEUE_PRIORITY_HIGH 高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 默认
DISPATCH_QUEUE_PRIORITY_LOW 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台执行

注意⚠️:

Main Dispatch Queue和Global Dispatch Queue中执行dispatch_release函数和dispatch_retain函数不会引起任何变化

  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  2. //全局队列
  3. NSLog(@"currentThread---%@",[NSThread currentThread]);
  4. dispatch_async(dispatch_get_main_queue(), ^{
  5. // 主队列
  6. NSLog(@"currentThread---%@",[NSThread currentThread]);
  7. });
  8. });

(4)dispatch_set_target_queue

dispatch_set_target_queue函数主要作用是变更Dispatch Queue的执行优先级。

  1. dispatch_queue_t serialQueue = dispatch_queue_create("com.baidu.xxx", NULL);
  2. dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  3. // 第一个参数是要变更执行优先级的Queue
  4. // 第二个参数是指定执行优先级的目标
  5. dispatch_set_target_queue(serialQueue, globalQueue);

第一个参数不能是系统指定的Main Dispatch Queue和Global Dispatch Queue,因为不确定最终发生了什么。
如果在多个Serial Dispatch Queue中使用dispatch_set_target_queue函数指定目标为某一个Serial Dispatch Queue,那么原先本应并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能执行一个处理。所以dispatch_set_target_queue函数可以防止多个并行执行的Serial Dispatch Queue按照目标一次执行。

(5)dispatch_after

dispatch_after并不是指定时间后执行处理,而是在指定时间追加处理到队列中。

  1. // 延迟3秒添加到队列中
  2. dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
  3. dispatch_after(time, dispatch_get_main_queue() , ^{
  4. NSLog(@"3s after");
  5. });

上述代码,是将block处理在3秒后添加到主队列中,因为主队列是在Runloop中执行的,所以每隔1/60执行一次runloop,所以Block最快在3秒后执行,最慢在3+1/60秒之后执行
dispatch_after函数参数说明:

  • 第一个参数是指定时间。这个值可以由dispatch_time和dispatch_walltime函数生成。
  • 第二个参数是需要追加到的队列
  • 第三个参数block是需要执行的处理

dispatch_time函数通常用于计算相对时间。
dispatch_walltime函数用于计算绝对时间,根据系统时钟创建绝对时间。

  1. // 第一个参数是一个时间结构体,如果传入NULL,方法会默认使用当前时间
  2. // 第二个参数是单位是纳秒
  3. dispatch_time_t wtime = dispatch_walltime(NULL, 1000 * 1000 * 1000)

(6)Dispatch Group

Dispatch Group可以监视加入的所有执行队列结束。一旦检测到所有任务执行结束,就可将结束的任务追加到Dispatch Queue中。

  1. dispatch_group_t group = dispatch_group_create();
  2. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  3. dispatch_group_async(group, queue, ^{
  4. NSLog(@"执行任务A");
  5. });
  6. dispatch_group_async(group, queue, ^{
  7. NSLog(@"执行任务B");
  8. });
  9. dispatch_group_async(group, queue, ^{
  10. NSLog(@"执行任务C");
  11. });
  12. dispatch_group_async(group, queue, ^{
  13. NSLog(@"执行任务D");
  14. });
  15. dispatch_group_notify(group, queue, ^{
  16. NSLog(@"最后执行");
  17. });
  18. // 第一个参数是group
  19. // 第二个参数是等待时间(超时),DISPATCH_TIME_FOREVER代表永不超时
  20. dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
  21. NSLog(@"group 执行结束");

最后执行结果:

2021-02-23 16:57:19.582588+0800 GCD演示[11718:280292] 执行任务B 2021-02-23 16:57:19.582620+0800 GCD演示[11718:280297] 执行任务C 2021-02-23 16:57:19.582617+0800 GCD演示[11718:280290] 执行任务D 2021-02-23 16:57:19.582584+0800 GCD演示[11718:280291] 执行任务A 2021-02-23 16:57:19.582772+0800 GCD演示[11718:280297] 最后执行 2021-02-23 16:57:19.582781+0800 GCD演示[11718:280220] group 执行结束

dispatch_group_async函数与dispatch_async函数相同。
dispatch_group_notify函数会在group中所有执行队列完成之后追加到最后队列中。不管该函数指定的Dispatch Queue是否是哪个,只要属于Dispatch Group中的所有队列都执行结束在追加该函数的Block到队列时。
dispatch_group_wait函数等待全部处理执行结束。如果返回值不为0,则在指定时间之后,还存在队列正在执行,如果返回值为0,则代表执行队列已经全部执行结束。

(7)dispatch_barrier_async

使用dispatch_barrier_async同新创建的Concurrent Dispatch Queue一起使用,可以实现高效率的数据库访问和文件访问。
dispatch_barrier_async函数能避免数据竞争问题

  1. dispatch_queue_t queue = dispatch_queue_create("com.queue", DISPATCH_QUEUE_CONCURRENT);
  2. dispatch_async(queue, ^{
  3. NSLog(@"执行任务A");
  4. });
  5. dispatch_async(queue, ^{
  6. NSLog(@"执行任务B");
  7. });
  8. // 该函数会在任务A和任务B执行完成之后再执行
  9. // 并且该函数执行完成之后才会执行后面的任务
  10. dispatch_barrier_async(queue, ^{
  11. NSLog(@"barrier执行任务");
  12. });
  13. dispatch_async(queue, ^{
  14. NSLog(@"执行任务C");
  15. });
  16. dispatch_async(queue, ^{
  17. NSLog(@"执行任务D");
  18. });
  19. dispatch_async(queue, ^{
  20. NSLog(@"执行任务E");
  21. });

输出结果:

2021-02-23 17:22:31.147158+0800 GCD演示[12304:298523] 执行任务A 2021-02-23 17:22:31.147169+0800 GCD演示[12304:298521] 执行任务B 2021-02-23 17:22:31.147309+0800 GCD演示[12304:298521] barrier执行任务 2021-02-23 17:22:31.147420+0800 GCD演示[12304:298521] 执行任务C 2021-02-23 17:22:31.147423+0800 GCD演示[12304:298523] 执行任务D 2021-02-23 17:22:31.147434+0800 GCD演示[12304:298529] 执行任务E

(8)dispatch_sync

dispatch_sync同步函数。
dispatch_sync使用主队列会造成死锁问题。同样的,与串行队列一起使用也会造成死锁问题。
下面几种死锁的情况:

  1. // 情况一
  2. dispatch_sync(dispatch_get_main_queue(), ^{
  3. NSLog(@"AAAA");
  4. });
  5. // 情况二
  6. dispatch_async(dispatch_get_main_queue(), ^{
  7. dispatch_sync(dispatch_get_main_queue(), ^{
  8. NSLog(@"BBB");
  9. });
  10. });
  11. // 情况三
  12. dispatch_queue_t queue = dispatch_queue_create("com.xxx", NULL);
  13. dispatch_async(queue, ^{
  14. dispatch_sync(queue, ^{
  15. NSLog(@"BBB");
  16. });
  17. });

(9)dispatch_apply

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

  1. dispatch_apply(10, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),^(size_t index){
  2. NSLog(@"%d",index);
  3. });

dispatch_apply参数说明:

  • 参数一:执行重复次数
  • 参数二:追加任务的执行队列
  • 参数三:执行任务处理的Block

(10)dispatch_suspend / dispatch_resume

dispatch_suspend函数:挂起指定的队列。
dispatch_resume函数:恢复挂起的指定队列。
这两个函数对已经执行的队列没有任何的影响。挂起尚未执行的处理将停止,在恢复之后才可以继续执行。

(11)Dispatch Semephore

Dispatch Semephore是GCD中的信号量。是持有计数的信号,Semephore使用计数来实现该功能,计数为0时等待,计数为1或大于1时,减去1而不等待。
核心方法:

  • dispatch_semaphore_create(long value): 创建一个信号量
  • dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout):接收一个信号和等待时间,若信号的信号量为0,则会阻塞当前线程,直到信号量大于0或者超过设定时间值;若信号量大于0,则会使信号量减1并返回,程序继续住下执行。
  • dispatch_semaphore_signal(dispatch_semaphore_t dsema): 使信号量加1并返回

线程同步

利用信号量的特性,先创建信号量为0 在需要保持同步的地方,使用dispatch_semaphore_wait等待线程完成 在子线程完成之后调用dispatch_semaphore_signal去触发等待往下执行

示例代码

  1. // 保持线程同步
  2. dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); // 关键,设置信号量为0
  3. // 该函数等待semaphore的计数大于或等于1
  4. __block int a = 10;
  5. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  6. a = 20;
  7. // 触发信号+1
  8. dispatch_semaphore_signal(semaphore);
  9. });
  10. long result = dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
  11. if (result == 0) {
  12. // 成功等待返回
  13. NSLog(@"%zd",a);
  14. } else {
  15. // 超过设定时长
  16. }

打印结果是 20,所以到result之后,需要等着子线程执行完成之后才往下走。

线程加锁

  • 创建信号量为1的信号
  • 在需要加锁的地方之前调用dispatch_semaphore_wait
  • 在需要解锁的地方使用dispatch_semaphore_signal

示例代码:

  1. // 线程加锁
  2. dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
  3. __block int a = 100;
  4. for (int i = 0; i < 100; i ++) {
  5. // for循环用于开辟多个线程
  6. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  7. if (a > 0) {
  8. dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 相当于加锁
  9. a -= 1;
  10. NSLog(@"%zd",a);
  11. dispatch_semaphore_signal(semaphore); //相当于解锁
  12. }
  13. });
  14. }

(12)dispatch_once

dispatch_once函数保证在应用程序执行中只执行一次指定处理。
一般常用于单利模式

  1. + (instancetype)shared {
  2. static NSObject *instance;
  3. static dispatch_once_t once;
  4. dispatch_once(&once, ^{
  5. instance = [[NSObject alloc] init];
  6. });
  7. return instance;
  8. }

(13)Dispatch I/O

dispatch_io_create 函数生成Dispatch I/O,并指定发生error时用来执行处理的block,以及执行该block的Dispatch Queue。
dispatch_io_set_low_water 函数设置一次读取的大小
dispatch_io_read 函数使用Global Dispatch Queue 开始并发读取。每当各个分割的文件块读取结束时,将含有文件块数据的 Dispatch Data(这里指pipedata) 传递给 “ dispatch_io_read 函数指定的读取结束时回调用的block ”,这个block拿到每一块读取好的 Dispatch Data(这里指pipe data),然后进行合并处理。
如果想提高文件读取速度,可以尝试使用 Dispatch I/O.
以下源码来源:https://opensource.apple.com/source/Libc/Libc-763.11/gen/asl.c

  1. static int
  2. _asl_auxiliary(aslmsg msg, const char *title, const char *uti, const char *url, int *out_fd)
  3. {
  4. asl_msg_t *merged_msg;
  5. asl_msg_aux_t aux;
  6. asl_msg_aux_0_t aux0;
  7. fileport_t fileport;
  8. kern_return_t kstatus;
  9. uint32_t outlen, newurllen, len, where;
  10. int status, fd, fdpair[2];
  11. caddr_t out, newurl;
  12. dispatch_queue_t pipe_q;
  13. dispatch_io_t pipe_channel;
  14. dispatch_semaphore_t sem;
  15. /* ..... 此处省略若干代码.....*/
  16. // 创建串行队列
  17. pipe_q = dispatch_queue_create("PipeQ", NULL);
  18. // 创建 Dispatch I/O
  19. pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
  20. close(fd);
  21. });
  22. *out_fd = fdpair[1];
  23. // 该函数设定一次读取的大小(分割大小)
  24. dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
  25. //
  26. dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
  27. if (err == 0) // err等于0 说明读取无误
  28. {
  29. // 读取完“单个文件块”的大小
  30. size_t len = dispatch_data_get_size(pipedata);
  31. if (len > 0)
  32. {
  33. // 定义一个字节数组bytes
  34. const char *bytes = NULL;
  35. char *encoded;
  36. dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
  37. encoded = asl_core_encode_buffer(bytes, len);
  38. asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
  39. free(encoded);
  40. _asl_send_message(NULL, merged_msg, -1, NULL);
  41. asl_msg_release(merged_msg);
  42. dispatch_release(md);
  43. }
  44. }
  45. if (done)
  46. {
  47. dispatch_semaphore_signal(sem);
  48. dispatch_release(pipe_channel);
  49. dispatch_release(pipe_q);
  50. }
  51. });
  52. }

(14)Dispatch Source

dispatch source是基础数据类型,协调特定底层系统事件的处理
Dispatch source替代了异步回调函数,来处理系统相关的事件。
当你配置一个dispatch source时,你指定要监测的事件、dispatch queue、以及处理事件的代码(block或函数)。
当事件发生时,dispatch source会提交你的block或函数到指定的queue去执行和手工提交到queue的任务不同,dispatch source为应用提供连续的事件源。除非你显式地取消,dispatch source会一直保留与dispatch queue的关联。只要相应的事件发生,就会提交关联的代码到dispatch queue去执行。为了防止事件积压到dispatch queue,dispatch source实现了事件合并机制。 如果新事件在上一个事件处理器出列并执行之前到达,dispatch source会将新旧事件的数据合并。 根据事件类型的不同,合并操作可能会替换旧事件,或者更新旧事件的信息。
Dispatch是BSD系内核惯有功能kqueue的包装。kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。其CPU负荷非常小,尽量不占用资源。kqueue可以说是应用程序处理XUN内核中发生的各种事件的方法中最优秀的一种。
Dispatch Source的种类

名称 内容
DISPATCH_SOURCE_TYPE_DATA_ADD 数据增加
DISPATCH_SOURCE_TYPE_DATA_OR 获取的内容进行OR运算
DISPATCH_SOURCE_TYPE_DATA_REPLACE 获取的内容替换
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接受
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存压力
DISPATCH_SOURCE_TYPE_PROC 检测到与进程相关的事件
DISPATCH_SOURCE_TYPE_READ 可读取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL 接受信号
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件系统有变更
DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像
DISPATCH_MACH_SEND_DEAD

自定义定时器

  1. __block int a = 0;
  2. dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
  3. // 设定 10秒间隔,允许1秒延迟
  4. dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
  5. dispatch_source_set_event_handler(timer, ^{
  6. // 定时任务
  7. NSLog(@"event");
  8. a = a + 1;
  9. if (a > 10) { //满足某一条件之后,取消定时器
  10. // 取消Dispatch Source
  11. dispatch_source_cancel(timer);
  12. }
  13. });
  14. // 指定取消定时器的任务处理
  15. dispatch_source_set_cancel_handler(timer, ^{
  16. NSLog(@"cancel timer");
  17. });
  18. // 启动定时器
  19. dispatch_resume(timer);