首页 > 代码库 > iOS核心笔记——多线程-GCD

iOS核心笔记——多线程-GCD

1、GCD简介:

?了解:GCD全称为“Grand Central Dispatch”,纯C语言,GCD提供了非常多功能强大的函数;GCD中所有的函数都包含于Libdispatch库中。


1-1、使用GCD的优势:

?了解:1、GCD是苹果公司为多核的并行运算提出的解决方案;

?了解:2、GCD会自动利用更多的CPU内核(例如:双核、四核);

?了解:3、GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)。


1-2、GCD两大核心概念:
名称作用:
任务 执行什么操作
队列 用来存放任务

1-3、GCD使用步骤:
步骤:作用:
?步骤一定制任务 确定想做的事情
?步骤二将任务添加到队列中 GCD会自动将队列中的任务取出,放到对应的线程中执行

2、GCD函数与队列:

2-1、GCD封装任务的函数:
名称作用:
?重要:同步函数(dispatch_sync() 不具备开线程的能力,不能开启新的线程;同步函数中封装的任务需要立即在当前线程中执行,即:当前线程中使用同步函数封装多个任务时,每一次执行同步函数时,会立即执行同步函数中的block。
?重要:异步函数(dispatch_async() 具备开启线程的能力,可以开启新的线程。异步函数封装任务时,如果有多个任务需要添加到同一个队列中时,将会等到所有的任务都添加到队列中时才开始从队列中取出任务执行。

2-2、GCD队列:
名称作用:
串行队列 队列中的任务必须按顺序执行。即:当前任务从队列中取出来之后必须执行完毕才能去队列中取出下一个任务。注意:串行队列中的任务需要立即在当前线程中执行。
并发队列 可以让多个任务并发执行,只有在异步函数中才能将任务并发执行;即:在异步函数中,从并发队列中当取出当前任务后,又可以马上取出下一个任务,不用等待当前取出的任务执行完毕就可以接着取出第二个任务,结合异步函数可以实现多个任务放到多条子线程中并发执行。注意:当并发队列需要执行多个任务时,需要等待所有任务都添加到队列中才开始执行任务。
主队列 与主线程相关联的、特殊的串行队列,dispatch_get_main_queue()函数获取主队列,没有参数;注意:主队列中的任务必须在主线程中执行,且只有在主线程空闲时才会取出主队列中的任务执行。
全局并发队列 通过dispatch_get_global_queue()函数可以获取整个应用程序中本身默认存在的全局并发队列,全局并发队列对应有高优先级、默认优先级、低优先级和后台优先级一共四种类型的全局并发队列,我们只是选择其中的一个直接拿来用;而使用Create函数手动创建并发队列是从头开始去创建一个新的并发队列。

?备注:无论是串行队列、并发队列、全局并发队列、主队列,队列中的任务取出都遵循FIFO原则:先进先出,后进后出。


2-3、各队列获取方式:
1、串行队列(DISPATCH_QUEUE_SERIAL):

技术分享

1./* 参数说明
2. *
3. * 第一个参数:C语言的字符串 给队列起个名字(建议:com.hhf.www.DownloadQueue)
4. * 第二个参数:队列类型
5. * DISPATCH_QUEUE_CONCURRENT 并发队列
6. * DISPATCH_QUEUE_SERIAL 串行队列
7. */
8. dispatch_queue_t queue = dispatch_queue_create("com.hhf.www.DownloadQueue", DISPATCH_QUEUE_SERIAL);
9.

2、并发队列(DISPATCH_QUEUE_CONCURRENT):

技术分享

1./* 参数说明
2. *
3. * 第一个参数:C语言的字符串 给队列起个名字(建议:com.hhf.www.DownloadQueue)
4. * 第二个参数:类型
5. * DISPATCH_QUEUE_CONCURRENT 并发队列
6. * DISPATCH_QUEUE_SERIAL 串行队列
7. */
8. dispatch_queue_t queue = dispatch_queue_create("com.hhf.www.DownloadQueue", DISPATCH_QUEUE_CONCURRENT);

3、主队列:

技术分享

1.  // 1. 获取主队列
2. dispatch_queue_t queue = dispatch_get_main_queue();

4、全局并发队列:

技术分享

1.// 1. 获取全局并发队列
2./* 参数说明
3. *
4. * 第一个参数: 全局并发队列优先级
5. * 第二个参数: 苹果预留参数, 传入0即可
6. * /
7. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

?备注:根据队列优先级不同,对应有高优先级、默认优先级、低优先级和后台优先级一共四种全局并发队列。

如下图所示技术分享


3、GCD中函数与队列多种组合模式:

3-1、函数与队列组合模式:
组合:备注
同步函数 + 串行队列 不会开启子线程,所有的任务在当前线程中串行执行(即:任务按顺序执行
同步函数 + 并发队列 不会开启子线程,所有的任务在当前线程中串行执行(即:任务按顺序执行
异步函数 + 串行队列 会开启一条子线程,所有的任务都在该子线程中串行执行(即:任务按顺序执行
异步函数 + 并发队列 会开启多条子线程,所有的任务并发执行?重要:子线程的数量并不是由任务的数量决定,而是由GCD内部自动决定;GCD认为需要几条线程合适就会开启几条线程。
同步函数 + 主队列(当前线程:主线程) 死锁,因为主队列需要等待主线程空闲,而同步函数中的任务需要立即在当前线程中执行;而主线程又正在执行该同步函数,只有等待同步函数执行完毕之后才能执行主队列中的任务;所以,造成循环等待。如果当前线程为子线程,且主线程空闲则不会产生死锁
异步函数 + 主队列 不会开启新的线程,所有的任务都在主线程中串行执行

3-2、组合模式示例:
1、同步函数 + 串行队列:
1./**
2. 同步函数 + 串行队列: 不会开启新的线程, 任务串行执行
3. */
4.- (void)synSerial{
5. // 1. 创建串行队列
6. dispatch_queue_t queue = dispatch_queue_create("串行队列", DISPATCH_QUEUE_SERIAL);
7. // 2. 同步函数中执行任务
8. dispatch_sync(queue, ^{
9. NSLog(@"1: %@", [NSThread currentThread]);
10. });
11.
12. dispatch_sync(queue, ^{
13. NSLog(@"2: %@", [NSThread currentThread]);
14. });
15.
16. dispatch_sync(queue, ^{
17. NSLog(@"3: %@", [NSThread currentThread]);
18. });
19.}

?分析:因为使用的是同步函数封装任务,而任务是存放在串行队列中;所以,任务立即在当前线程中按顺序执行,即:当前线程执行任务一 -> 打印“1” -> 再执行任务二


2、同步函数 + 并发队列:
1./**
2. 同步函数 + 并发队列: 不会开启新的线程, 并发队列中所有任务按顺序在当前线程中执行
3. */
4.- (void)synConcurrent{
5. // 1. 获取全局并发队列
6. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
7.
8. // 2. 在同步函数中执行任务
9. dispatch_sync(queue, ^{
10. NSLog(@"1: %@", [NSThread currentThread]);
11. });
12.
13. dispatch_sync(queue, ^{
14. NSLog(@"2: %@", [NSThread currentThread]);
15. });
16.
17. dispatch_sync(queue, ^{
18. NSLog(@"3: %@", [NSThread currentThread]);
19. });
20.}

?分析:因为,封装函数为同步函数,所以,不会开启新的线程;虽然使用了并发队列,但是,只有一条线程,而线程执行任务是串行的,所以,所有的任务一个接着一个按顺序执行。


3、异步函数 + 串行队列:
1./**
2. 异步函数 + 串行队列: 只能开启一条新的子线程, 任务只能在当前子线程中一个接着一个按顺序执行
3. */
4.- (void)asynSerial{
5. // 1. 创建串行队列, 串行队列只能手动创建(主队列也是特殊的串行队列)
6. dispatch_queue_t queue = dispatch_queue_create("HH", DISPATCH_QUEUE_SERIAL);
7.
8. // 2. 异步函数执行任务
9. dispatch_async(queue, ^{
10. NSLog(@"%@", [NSThread currentThread]);
11. });
12.
13. dispatch_async(queue, ^{
14. NSLog(@"%@", [NSThread currentThread]);
15. });
16.
17. dispatch_async(queue, ^{
18. NSLog(@"%@", [NSThread currentThread]);
19. });
20.
21. NSLog(@"---- end ----");
22.}

?分析:因为封装函数为异步函数,所以,会开启一条新的线程;而任务是添加到串行队列中,所以,等所有任务都添加到串行队列中时,将任务按顺序一个一个的取出执行(异步函数:导致需要等待所有任务都添加到队列中才能执行,串行队列:任务按顺序执行)。


4、异步函数 + 并发队列:
1./**
2. 异步函数 + 并发队列: 开启多条新线程, 任务并发执行
3. */
4.- (void)asynConcurrent{
5. // 1. 获取并发队列两种方式: ①获取全局并发队列; ②手动创建并发队列
6. // long identifier : 队列优先级 ; unsigned long flags : 苹果预留参数
7.// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
8. dispatch_queue_t queue = dispatch_queue_create("HH", DISPATCH_QUEUE_CONCURRENT);
9.
10. // 2. 使用异步函数执行队列中的任务
11. dispatch_async(queue, ^{
12. NSLog(@"%@", [NSThread currentThread]);
13. });
14.
15. dispatch_async(queue, ^{
16. NSLog(@"%@", [NSThread currentThread]);
17. });
18.
19. dispatch_async(queue, ^{
20. NSLog(@"%@", [NSThread currentThread]);
21. });
22.
23. NSLog(@"--- end ---");
24.}

?分析:异步函数所以会开启多条子线程,并发队列可以将任务取出放到对应子线程中之后马上又从队列中取出下一个任务放到新的子线程中执行;但是,执行任务因为是异步函数必须等到所有任务都添加到队列。


5、同步函数 + 主队列:
1./**
2. 同步函数 + 主队列: 在主线程使用同步函数执行主队列中的任务造成循环等待
3. */
4.- (void)synMainQueue{
5. // 1. 获取主队列
6. dispatch_queue_t queue = dispatch_get_main_queue();
7.
8. // 2. 在同步函数中执行任务
9. dispatch_sync(queue, ^{
10. NSLog(@"%@", [NSThread currentThread]);
11. });
12.
13. NSLog(@"--- end ---");
14.}

?分析:主队列的特性是:主队列中的任务都必须在主线程中执行。根据当前任务所在线程的不同,有两种执行结果:①当前线程为主线程:主线程和主队列循环等待造成死锁,原因:当前封装任务的同步函数是在主线程中执行(即:dispatch_sync()函数在主线程中),而同步函数封装的任务必须立即在当前线程中执行;但是,同步函数封装的任务是添加到主队列中,主队列中的任务只有等待主线程空闲时才会被主线程调度,但是,主线程此时正在执行同步函数;且同步函数中的任务没有得到调度造成同步函数没有执行完毕,所以,主线程一直处于忙碌状态;即:主线程等待同步函数执行完毕,而同步函数又等待主线程调度其封装到主队列中的任务;而主队列又在等待主线程空闲,因此,造成了:主线程-> 同步函数->主队列中的任务->主线程,循环等待;产生死锁。②当前线程为子线程:因为同步函数是在子线程中执行,所以,假如主线程空闲,此时,在子线程中将任务使用同步函数封装之后添加到主队列中;主线程空闲,子线程中往主队列中添加的任务得到主线程的调度执行,所以,同步函数能够执行完毕,并不会造成死锁。


6、同步函数 + 主队列:
1./**
2. 异步函数 + 主队列: 不会开启新线程, 任务串行执行
3. */
4.- (void)asynMainQueue{
5. // 1. 获取主队列
6. dispatch_queue_t queue = dispatch_get_main_queue();
7.
8. // 2. 在异步函数中执行任务
9. dispatch_async(queue, ^{
10. NSLog(@"%@", [NSThread currentThread]);
11. });
12.
13. dispatch_async(queue, ^{
14. NSLog(@"%@", [NSThread currentThread]);
15. });
16.
17. dispatch_async(queue, ^{
18. NSLog(@"%@", [NSThread currentThread]);
19. });
20.}

?分析:无论主线程是否空闲,所有的任务都能够在主线程中执行完毕。因为,当前使用异步函数封装任务;所以,需要将所有的任务都添加到主队列之后才能等待主线程的调度执行。假如,当前线程为主线程,当所有的任务都添加到主队列之后;异步函数执行完毕,此时,主线程空闲,主队列中的任务得到主线程的调度。


7、GCD线程间通信:

技术分享


4、总结:

4-1、GCD与 NSThread 的对比:
  • 1、所有的代码写在一起的,让代码更加简单,易于阅读和维护;
  • 2、NSThread 通过@selector指定要执行的方法,代码分散;
  • 3、GCD 通过block指定要执行的代码,代码集中;
  • 4、使用GCD不需要管理线程的创建/销毁/复用的过程,程序员不用关心线程的生命周期;
  • 5、如果要开多个线程NSThread必须实例化多个线程对象;
  • 6、NSThread使用NSObject的分类方法实现的线程间通讯,GCD使用block进行通讯。

4-2、全局队列 & 并发队列的区别:
  • 全局队列又叫全局并发队列.
  • 是系统为了方便程序员开发提供的,其工作表现与并发队列一致
  • 全局队列:
    • 没有名称;
    • 无论 MRC & ARC 都不需要考虑释放;
    • 日常开发中,建议使用”全局队列”。
  • 并发队列:
    • 有名字,和 NSThreadname 属性作用类似;
    • 如果在 MRC 开发时,需要使用 dispatch_release(q); 释放相应的对象;
    • 开发第三方框架时,建议使用并发队列。

4-3、全局队列参数:
  1. 服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

    • iOS 8.0(新增,暂时不能用):

      • QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
      • QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
      • QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)
      • QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!)
      • QOS_CLASS_BACKGROUND 0x09, 后台
      • QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配
    • iOS 7.0:

      • DISPATCH_QUEUE_PRIORITY_HIGH —> 2,高优先级
      • DISPATCH_QUEUE_PRIORITY_DEFAULT —> 0,默认优先级
      • DISPATCH_QUEUE_PRIORITY_LOW —> (-2),低优先级
      • DISPATCH_QUEUE_PRIORITY_BACKGROUND —> INT16_MIN,后台优先级

  1. 为未来保留使用的,应该永远传入0

结论:如果要适配iOS 7.0&iOS8.0,使用以下代码:
dispatch_get_global_queue(0, 0);


4-4、任务和队列:
  • 同步和异步决定了要不要开启新的线程 (同步不开,异步开):

    • 同步:在当前线程中执行任务,不具备开启新线程的能力;
    • 异步:在新的线程中执行任务,具备开启新线程的能力。
  • 串行和并发决定了任务的执行方式:

    • 串行:一个任务执行完毕后,再执行下一个任务;
    • 并发:多个任务并发(同时)执行。
  • 当任务是异步的时候,队列决定了开启多少条线程:

    • 串行队列 : 只开一条;
    • 并发队列 : 可以开启多条。

任务和队列技术分享


iOS核心笔记——多线程-GCD