1. GCD简介

  • GCD全称是Grand Central Dispatch

  • C语言,提供例如非常强大的函数

  • GCD作用:将任务添加到队列,并指定任务执行的函数

1.1 GCD优势

  • GCD是苹果公司为多核的并行运算提出的解决方案

  • GCD会自动利用更多的CPU内核(比如双核、四核)

  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

1.2 GCD基础

GCD的基本用法

  1. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  2. NSLog(@"%@",[NSThread currentThread]);
  3. });

还原最基础的写法,可拆分成任务、队列和函数

  1. - (void)syncTest{
  2. // 任务
  3. dispatch_block_t block = ^{
  4. NSLog(@"hello GCD");
  5. };
  6. // 串行队列
  7. dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
  8. // 函数
  9. dispatch_async(queue, block);
  10. }
  • 使用dispatch_block_t创建任务

    • 任务使用block封装

    • 任务的block没有参数,也没有返回值

  • 使用dispatch_queue_t创建队列

  • 将任务添加到队列,并指定执行任务的函数dispatch_async

2. 函数

GCD中执行任务的方式有两种,同步执行和异步执行

  • dispatch_sync:同步函数

  • dispatch_async:异步函数

2.1 dispatch_sync

同步函数dispatch_sync的特性:

  • 必须等待当前语句执行完毕,才会执行下一条语句

  • 不会开启线程,即不具备开启新线程的能力

  • 在当前线程中执行block任务

2.2 dispatch_async

异步函数dispatch_async的特性:

  • 不用等待当前语句执行完毕,就可以执行下一条语句

  • 会开启线程执行block任务,即具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关)

  • 异步是多线程的代名词

2.3 二者的区别

  • 是否等待队列的任务执行完毕

  • 是否具备开启新线程的能力

3. 队列

队列分为串行队列和并发队列,用来存放任务。队列是一种数据结构,属于特殊的线性表,遵循先进先出(FIFC)原则。新任务被插入到队尾,而任务的读取从队首开始。每读取一个任务,则队列中释放一个任务

GCD中,还提供了两个特殊的队列,分别是主队列和全局并发队列。主队列属于串行队列,而全局并发队列属于并发队列

队列和线程并没有关系,队列负责任务的调度,任务的执行依赖于线程,优先调度的任务不一定优先执行

3.1 串行队列

image.png

串行队列:Serial Dispatch Queue

  1. dispatch_queue_t serialQueue1 = dispatch_queue_create("com.lg.serial", NULL);
  2. dispatch_queue_t serialQueue2 = dispatch_queue_create("com.lg.serial", DISPATCH_QUEUE_SERIAL);
  • 每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个

  • 只开启一个线程,同一时刻只调度一个任务执行

  • 使用DISPATCH_QUEUE_SERIAL创建串行队列

  • DISPATCH_QUEUE_SERIAL也可传入NULL,默认创建为串行队列

3.2 并发队列

image.png

并发队列:Concurrent Dispatch Queue

  1. dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lg.concurrent", DISPATCH_QUEUE_CONCURRENT);
  • 一次可以并发执行多个任务

  • 开启多个线程,同一时刻可以调度多个任务执行

  • 使用DISPATCH_QUEUE_CONCURRENT创建并发队列

  • 并发队列的并发功能只有在异步函数下才有效

3.3 主队列

主队列:Main Dispatch Queue

  1. dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • 主队列:GCD提供的特殊的串行队列

  • 专门用来在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前自动创建

  • 不会开启线程

  • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度

  • 使用dispatch_get_main_queue获得主队列

  • 通常在返回主线程更新UI时使用

3.4 全局并发队列

全局并发队列:Global Dispatch Queue

  1. dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
  • GCD提供的默认的并发队列

  • 为了方便开发者使用,苹果提供了全局队列

  • 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列

  • 使用dispatch_get_global_queue获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)

    • 参数1表示队列优先级,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT = 0,被服务质量quality of service取代

    • 参数2为标记,是为了未来使用保留的。所以这个参数应该永远指定为0

优先级从高到低,对应服务质量:

  • DISPATCH_QUEUE_PRIORITY_HIGHQOS_CLASS_USER_INITIATED

  • DISPATCH_QUEUE_PRIORITY_DEFAULTQOS_CLASS_DEFAULT

  • DISPATCH_QUEUE_PRIORITY_LOWQOS_CLASS_UTILITY

  • DISPATCH_QUEUE_PRIORITY_BACKGROUNDQOS_CLASS_BACKGROUND

日常开发中,主队列 + 全局并发队列的使用:

  1. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  2. //执行耗时操作
  3. dispatch_async(dispatch_get_main_queue(), ^{
  4. //回到主线程进行UI操作
  5. });
  6. });

4. 队列与函数的搭配使用

队列/函数 同步函数 异步函数
串行队列 不会开启线程,在当前线程执行
任务串行执行,一个接一个
开启新线程
任务串行执行,一个接一个
并发队列 不会开启线程,在当前线程执行
任务串行执行,一个接一个
开启新线程
任务异步执行,没有顺序,和CPU调度有关
主队列 死锁 不会开启线程,在当前线程执行
任务串行执行,一个接一个
全局队列 不会开启线程,在当前线程执行
任务串行执行,一个接一个
开启新线程
任务异步执行,没有顺序,和CPU调度有关

4.1 串行队列

4.1.1 同步函数

  1. // 同步串行队列
  2. - (void)serialSyncTest{
  3. dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_SERIAL);
  4. for (int i = 0; i<5; i++) {
  5. dispatch_sync(queue, ^{
  6. NSLog(@"%d:%@",i,[NSThread currentThread]);
  7. });
  8. }
  9. NSLog(@"hello queue");
  10. }
  11. -------------------------
  12. //输出以下内容:
  13. 0:<NSThread: 0x2807e83c0>{number = 1, name = main}
  14. 1:<NSThread: 0x2807e83c0>{number = 1, name = main}
  15. 2:<NSThread: 0x2807e83c0>{number = 1, name = main}
  16. 3:<NSThread: 0x2807e83c0>{number = 1, name = main}
  17. 4:<NSThread: 0x2807e83c0>{number = 1, name = main}
  18. hello queue
  • 不会开启线程,在当前线程执行
  • 任务串行执行,一个接一个

4.1.2 异步函数

  1. // 异步串行队列
  2. - (void)serialAsyncTest{
  3. dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_SERIAL);
  4. for (int i = 0; i<5; i++) {
  5. dispatch_async(queue, ^{
  6. NSLog(@"%d:%@",i,[NSThread currentThread]);
  7. });
  8. }
  9. NSLog(@"hello queue");
  10. }
  11. -------------------------
  12. //输出以下内容:
  13. hello queue
  14. 0:<NSThread: 0x282ba5740>{number = 5, name = (null)}
  15. 1:<NSThread: 0x282ba5740>{number = 5, name = (null)}
  16. 2:<NSThread: 0x282ba5740>{number = 5, name = (null)}
  17. 3:<NSThread: 0x282ba5740>{number = 5, name = (null)}
  18. 4:<NSThread: 0x282ba5740>{number = 5, name = (null)}
  • 开启新线程
  • 任务串行执行,一个接一个

4.2 并发队列

4.2.1 同步函数

  1. // 同步并发队列
  2. - (void)concurrentSyncTest{
  3. dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
  4. for (int i = 0; i<5; i++) {
  5. dispatch_sync(queue, ^{
  6. NSLog(@"%d-%@",i,[NSThread currentThread]);
  7. });
  8. }
  9. NSLog(@"hello queue");
  10. }
  11. -------------------------
  12. //输出以下内容:
  13. 0:<NSThread: 0x2807e83c0>{number = 1, name = main}
  14. 1:<NSThread: 0x2807e83c0>{number = 1, name = main}
  15. 2:<NSThread: 0x2807e83c0>{number = 1, name = main}
  16. 3:<NSThread: 0x2807e83c0>{number = 1, name = main}
  17. 4:<NSThread: 0x2807e83c0>{number = 1, name = main}
  18. hello queue
  • 不会开启线程,在当前线程执行
  • 任务串行执行,一个接一个

4.2.2 异步函数

  1. // 异步并发队列
  2. - (void)concurrentAsyncTest{
  3. dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
  4. for (int i = 0; i<5; i++) {
  5. dispatch_async(queue, ^{
  6. NSLog(@"%d:%@",i,[NSThread currentThread]);
  7. });
  8. }
  9. NSLog(@"hello queue");
  10. }
  11. -------------------------
  12. //输出以下内容:
  13. hello queue
  14. 1:<NSThread: 0x2801ef300>{number = 10, name = (null)}
  15. 0:<NSThread: 0x2801ee300>{number = 8, name = (null)}
  16. 2:<NSThread: 0x2801e9b00>{number = 9, name = (null)}
  17. 3:<NSThread: 0x2801edf00>{number = 6, name = (null)}
  18. 4:<NSThread: 0x2801ee7c0>{number = 4, name = (null)}
  • 开启新线程
  • 任务异步执行,没有顺序,和CPU调度有关

4.3 主队列

4.3.1 同步函数

  1. // 同步主队列
  2. - (void)mainSyncTest{
  3. dispatch_queue_t queue = dispatch_get_main_queue();
  4. for (int i = 0; i<5; i++) {
  5. dispatch_sync(queue, ^{
  6. NSLog(@"%d:%@",i,[NSThread currentThread]);
  7. });
  8. }
  9. NSLog(@"hello queue");
  10. }
  11. -------------------------
  12. //程序崩溃:
  13. Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1010b4be0)
  • 主队列的作用,专门用来在主线程上调度任务的串行队列

  • 主队列中增加同步函数,导致主线程需要等待同步函数完成后再执行

  • 由于主队列是特殊的串行队列,同步函数需要等待主线程完成后再执行

  • 所以,两个任务相互等待,产生死锁

4.3.2 异步函数

  1. // 异步主队列
  2. - (void)mainAsyncTest{
  3. dispatch_queue_t queue = dispatch_get_main_queue();
  4. for (int i = 0; i<5; i++) {
  5. dispatch_async(queue, ^{
  6. NSLog(@"%d:%@",i,[NSThread currentThread]);
  7. });
  8. }
  9. NSLog(@"hello queue");
  10. }
  11. -------------------------
  12. //输出以下内容:
  13. hello queue
  14. 0:<NSThread: 0x280ed0200>{number = 1, name = main}
  15. 1:<NSThread: 0x280ed0200>{number = 1, name = main}
  16. 2:<NSThread: 0x280ed0200>{number = 1, name = main}
  17. 3:<NSThread: 0x280ed0200>{number = 1, name = main}
  18. 4:<NSThread: 0x280ed0200>{number = 1, name = main}
  • 不会开启线程,在当前线程执行
  • 任务串行执行,一个接一个

4.4 全局队列

4.4.1 同步函数

  1. // 同步全局队列
  2. - (void)globalSyncTest{
  3. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  4. for (int i = 0; i<5; i++) {
  5. dispatch_sync(queue, ^{
  6. NSLog(@"%d:%@",i,[NSThread currentThread]);
  7. });
  8. }
  9. NSLog(@"hello queue");
  10. }
  11. -------------------------
  12. //输出以下内容:
  13. 0:<NSThread: 0x283a948c0>{number = 1, name = main}
  14. 1:<NSThread: 0x283a948c0>{number = 1, name = main}
  15. 2:<NSThread: 0x283a948c0>{number = 1, name = main}
  16. 3:<NSThread: 0x283a948c0>{number = 1, name = main}
  17. 4:<NSThread: 0x283a948c0>{number = 1, name = main}
  18. hello queue
  • 不会开启线程,在当前线程执行
  • 任务串行执行,一个接一个

4.4.2 异步函数

  1. // 异步全局队列
  2. - (void)globalAsyncTest{
  3. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  4. for (int i = 0; i<5; i++) {
  5. dispatch_async(queue, ^{
  6. NSLog(@"%d:%@",i,[NSThread currentThread]);
  7. });
  8. }
  9. NSLog(@"hello queue");
  10. }
  11. -------------------------
  12. //输出以下内容:
  13. hello queue
  14. 0:<NSThread: 0x2813099c0>{number = 9, name = (null)}
  15. 1:<NSThread: 0x281372b80>{number = 4, name = (null)}
  16. 2:<NSThread: 0x281371e40>{number = 10, name = (null)}
  17. 3:<NSThread: 0x281372080>{number = 6, name = (null)}
  18. 4:<NSThread: 0x281372c00>{number = 3, name = (null)}
  • 开启新线程
  • 任务异步执行,没有顺序,和CPU调度有关

4.5 线程死锁

所谓线程死锁,是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
image.png

  • 函数调用栈中,_dispatch_sync_f_slow即为死锁的异常

5. 面试题解析

5.1 题目1

  1. - (void)wbinterDemo{
  2. dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
  3. NSLog(@"1:%@",[NSThread currentThread]);
  4. dispatch_async(queue, ^{
  5. NSLog(@"2:%@",[NSThread currentThread]);
  6. dispatch_async(queue, ^{
  7. NSLog(@"3:%@",[NSThread currentThread]);
  8. });
  9. NSLog(@"4:%@",[NSThread currentThread]);
  10. });
  11. NSLog(@"5:%@",[NSThread currentThread]);
  12. }
  13. -------------------------
  14. //输出以下内容:
  15. 1:<NSThread: 0x2801a4900>{number = 1, name = main}
  16. 5:<NSThread: 0x2801a4900>{number = 1, name = main}
  17. 2:<NSThread: 0x2801e0580>{number = 6, name = (null)}
  18. 4:<NSThread: 0x2801e0580>{number = 6, name = (null)}
  19. 3:<NSThread: 0x2801e0580>{number = 6, name = (null)}

5.2 题目2

  1. - (void)wbinterDemo{
  2. dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
  3. NSLog(@"1:%@",[NSThread currentThread]);
  4. dispatch_async(queue, ^{
  5. NSLog(@"2:%@",[NSThread currentThread]);
  6. dispatch_sync(queue, ^{
  7. NSLog(@"3:%@",[NSThread currentThread]);
  8. });
  9. NSLog(@"4:%@",[NSThread currentThread]);
  10. });
  11. NSLog(@"5:%@",[NSThread currentThread]);
  12. }
  13. -------------------------
  14. //输出以下内容:
  15. 1:<NSThread: 0x281864900>{number = 1, name = main}
  16. 5:<NSThread: 0x281864900>{number = 1, name = main}
  17. 2:<NSThread: 0x28185ad40>{number = 5, name = (null)}
  18. 3:<NSThread: 0x28185ad40>{number = 5, name = (null)}
  19. 4:<NSThread: 0x28185ad40>{number = 5, name = (null)}

5.3 题目3

  1. - (void)wbinterDemo{
  2. dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
  3. NSLog(@"1:%@",[NSThread currentThread]);
  4. dispatch_async(queue, ^{
  5. NSLog(@"2:%@",[NSThread currentThread]);
  6. dispatch_sync(queue, ^{
  7. NSLog(@"3:%@",[NSThread currentThread]);
  8. });
  9. NSLog(@"4:%@",[NSThread currentThread]);
  10. });
  11. NSLog(@"5:%@",[NSThread currentThread]);
  12. }
  13. -------------------------
  14. //输出以下内容:
  15. 1:<NSThread: 0x281afc900>{number = 1, name = main}
  16. 5:<NSThread: 0x281afc900>{number = 1, name = main}
  17. 2:<NSThread: 0x281abdcc0>{number = 6, name = (null)}
  18. //程序崩溃:
  19. Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1010b4be0)
  • 15在主队列,不受影响

  • 异步函数先执行2,但是4要等待同步函数优先执行

  • 同步函数的特性“护犊子”,必须自身优先执行,然后才会执行下面的代码。所以异步函数的完成,必须等待同步函数先执行完成

  • 由于使用串行队列,同步函数又需要代码异步函数执行完成后才能执行

  • 所以,两个函数相互等待,产生死锁

5.4 题目4

  1. - (void)wbinterDemo{
  2. dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
  3. dispatch_async(queue, ^{
  4. NSLog(@"1");
  5. });
  6. dispatch_async(queue, ^{
  7. NSLog(@"2");
  8. });
  9. dispatch_sync(queue, ^{
  10. NSLog(@"3");
  11. });
  12. NSLog(@"0");
  13. dispatch_async(queue, ^{
  14. NSLog(@"7");
  15. });
  16. dispatch_async(queue, ^{
  17. NSLog(@"8");
  18. });
  19. dispatch_async(queue, ^{
  20. NSLog(@"9");
  21. });
  22. }
  • A:1230789
  • B:1237890
  • C:3120798
  • D:2137890

正确答案:A、C

  • 3在同步函数中,0在主线程,所以3一定在0的前面

  • 12在异步函数中,所以顺序不一定

  • 789一定会在30之后,但它们在异步函数中,所以顺序不一定

5.5 题目5

队列的类型有几种?

  1. //串行队列 - Serial Dispatch Queue
  2. dispatch_queue_t serialQueue = dispatch_queue_create("com.lg.cn", NULL);
  3. //并发队列 - Concurrent Dispatch Queue
  4. dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
  5. //主队列 - Main Dispatch Queue
  6. dispatch_queue_t mainQueue = dispatch_get_main_queue();
  7. //全局并发队列 - Global Dispatch Queue
  8. dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

正确答案:两种

  • 串行队列:serialQueuemainQueue

  • 并发队列:concurrentQueueglobalQueue

5.6 题目6

  1. @property (atomic, assign) int num;
  2. - (void)wbinterDemo{
  3. while (self.num < 5) {
  4. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  5. self.num++;
  6. });
  7. }
  8. NSLog(@"end : %d",self.num);
  9. }
  • 正确答案:>= 5,虽然num使用atomic修饰,但只能保证在自身线程中是安全的。当开启异步多线程,在while停止循环后,可能还会有线程进行num++,所有最终输出的结果大概率会

5.7 题目7

  1. - (void)wbinterDemo{
  2. for (int i= 0; i<10000; i++) {
  3. dispatch_async(dispatch_get_global_queue(0, 0), ^{
  4. self.num++;
  5. });
  6. }
  7. NSLog(@"end : %d",self.num);
  8. }
  • 正确答案:<= 10000

总结

GCD简介:

  • GCD全称是Grand Central Dispatch
  • C语言,提供例如非常强大的函数
  • GCD作用:将任务添加到队列,并指定任务执行的函数

GCD优势:

  • GCD是苹果公司为多核的并行运算提出的解决方案
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

GCD基础:

  • 使用dispatch_block_t创建任务
    • 任务使用block封装
    • 任务的block没有参数,也没有返回值
  • 使用dispatch_queue_t创建队列
  • 将任务添加到队列,并指定执行任务的函数dispatch_async

函数:

  • GCD中执行任务的方式有两种,同步执行和异步执行
    • dispatch_sync:同步函数
    • dispatch_async:异步函数

队列:

  • 队列分为串行队列和并发队列,用来存放任务。队列是一种数据结构,属于特殊的线性表,遵循先进先出(FIFC)原则。新任务被插入到队尾,而任务的读取从队首开始。每读取一个任务,则队列中释放一个任务
    • 串行队列:Serial Dispatch Queue
      • 每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个
      • 只开启一个线程,同一时刻只调度一个任务执行
      • 使用DISPATCH_QUEUE_SERIAL创建串行队列
      • DISPATCH_QUEUE_SERIAL也可传入NULL,默认创建为串行队列
    • 并发队列:Concurrent Dispatch Queue
      • 一次可以并发执行多个任务
      • 开启多个线程,同一时刻可以调度多个任务执行
      • 使用DISPATCH_QUEUE_CONCURRENT创建并发队列
      • 并发队列的并发功能只有在异步函数下才有效
  • GCD中,还提供了两个特殊的队列,分别是主队列和全局并发队列。主队列属于串行队列,而全局并发队列属于并发队列
    • 主队列
      • 主队列:GCD提供的特殊的串行队列
      • 专门用来在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前自动创建
      • 不会开启线程
      • 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
      • 使用dispatch_get_main_queue获得主队列
      • 通常在返回主线程更新UI时使用
    • 全局并发队列
      • GCD提供的默认的并发队列
      • 为了方便开发者使用,苹果提供了全局队列
      • 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
      • 使用dispatch_get_global_queue获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
  • 队列和线程并没有关系,队列负责任务的调度,任务的执行依赖于线程,优先调度的任务不一定优先执行

队列与函数的搭配使用:

  • 串行队列
    • 同步函数
      • 不会开启线程,在当前线程执行
      • 任务串行执行,一个接一个
      • 会产生堵塞
    • 异步函数
      • 开启新线程
      • 任务串行执行,一个接一个
  • 并发队列
    • 同步函数
      • 不会开启线程,在当前线程执行
      • 任务串行执行,一个接一个
    • 异步函数
      • 开启新线程
      • 任务异步执行,没有顺序,和CPU调度有关
  • 主队列
    • 同步函数
      • 死锁
    • 异步函数
      • 不会开启线程,在当前线程执行
      • 任务串行执行,一个接一个
  • 全局队列
    • 同步函数
      • 不会开启线程,在当前线程执行
      • 任务串行执行,一个接一个
    • 异步函数
      • 开启新线程
      • 任务异步执行,没有顺序,和CPU调度有关