本文由快学吧个人写作,以任何形式转载请表明原文出处。
一、GCD的概念
1. 基本概念
GCD全称 : Grand Central Dispatch
意为 : 多线程优化技术。从名称来看,更加直接,名称本意是中央调度,也就是说GCD将线程的创建和销毁,还有调用和阻塞全部归属到中央调度,不需要我们来管理。
GCD全部都是由C语言编写,提供了异常强大的函数。由于是C语言编写,性能非常不错。
推出GCD的原因 :
- 苹果公司为了解决多核的并行运算。
- GCD会自动的利用更多的CPU内存,比如GCD是可以自己来调用双核或者四核的
- GCD可以自动的管理线程的生命周期,比如线程的创建、线程的调度、线程的销毁。所以使用GCD就只需要告诉GCD需要做什么。
简单的说 :
GCD = 任务 + 队列(用来存放任务的) + 函数(用来执行任务的)
2. GCD中的任务
ini
dispatch_block_t mission_block = ^{
NSLog(@"这是一个GCD的任务");
};
GCD中所谓的任务就是一个block块。里面就是逻辑代码,或者说任务代码。
3. GCD中的队列
ini
dispatch_queue_t jd_queue = dispatch_queue_create("jd_gcd_queue", NULL);
GCD中的队列,本身就是一个特殊的线性表,遵循FIFO原则,也就是先进来的任务先执行,执行后任务就退出队列。

GCD的队列一共分为两种 : 串行队列和并发队列。
(1). 串行队列
串行队列,Serial Dispatch Queue
:
所谓串行,就是任务要一个接一个的排队执行,一个任务执行完毕了,再执行下一个,不许插队,串行队列不会开启新的线程。举个例子 : 串行队列就像一条生产线,任务就像生产线上的货物,一个货物接着一个货物,一个货物下去了,后面的货物才能下去。

(2). 并发队列
并发队列,Concurrent Dispatch Queue
:
所谓并发,就是多个任务可以一起执行,可以开启多条线程,多个任务一起执行。并发队列只有在
dispatch_async
函数下才有效。

4. GCD中的函数
GCD中有两个常用的函数 : 异步函数和同步函数。
scss
异步函数,开启多线程
dispatch_async(jd_queue, mission_block);
同步函数,不开启多线程
dispatch_sync(jd_queue, mission_block);
- 同步函数 :
同步函数不具备开辟新线程的能力。同步函数会将任务添加到指定的队列中,在同步函数添加的任务没有执行完成之前,其他的任务都要处于等待的状态中,直到同步函数添加的任务执行完成之后,才会执行其他的任务。
- 异步函数 :
异步函数具备开辟新线程的能力,但是否开启新的线程,何时开启新的线程则和队列相关。异步函数添加完任务之后不会等待任务执行完毕,异步函数只管添加任务和执行任务,不管添加的任务是否现在必须执行完成,不影响当前线程中其他任务的执行。
5. 举例


可以看到,对于同一个串行队列(队列的第二个参数是NULL,是串行队列),同一个任务 :
异步函数会开启线程,因为number是3。
同步函数不会开启线程,因为number是1,并且name是main,证明就是在主线程中执行的。
二、GCD的基本使用
1. 自定义GCD的队列创建
GCD创建队列使用的是dispatch_queue_create
函数 :

dispatch_queue_create
函数的返回值是一个队列。两个参数如下 :
const char *_Nullable label
:label是队列的名称,字符型,可以为空。是队列的唯一标识符。
dispatch_queue_attr_t _Nullable attr
:attr是队列的类型。一般有两个选择 :
(1).
DISPATCH_QUEUE_SERIAL
: 串行队列。
DISPATCH_QUEUE_SERIAL
是一个宏,本身是NULL,所以创建串行队列的时候,dispatch_queue_create
函数的第二个参数可以直接传NULL,就代表创建了一个串行队列。(2).
DISPATCH_QUEUE_CONCURRENT
: 并发队列。宏定义是
DISPATCH_GLOBAL_OBJECT(dispatch_queue_attr_t, \ _dispatch_queue_attr_concurrent)
举例 :
ini
// 创建一个串行队列
dispatch_queue_t jd_serial_queue = dispatch_queue_create("jd_serial_queue", DISPATCH_QUEUE_SERIAL);
// 创建一个并发队列
dispatch_queue_t jd_concurrent_queue = dispatch_queue_create("jd_concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
2. GCD提供的队列
GCD提供了两个特殊的队列 : 主队列和全局并发队列。
- 主队列 :
获取方式 :
dispatch_get_main_queue()
特点 : 所有加入到主队列中的任务都会在主线程中执行。主队列相当于是一个特殊的串行队列。
- 全局并发队列 :
获取方式 :
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
特点 : 全局并发队列是一个特殊的并发队列。是默认创建的并发队列,如果没有特殊的需求,可以直接使用全局并发队列执行一些任务。
对于全局并发队列,两个参数的含义 :
(1).
identifier
: 全局并发队列的优先级,可以选择的宏如下 :
DISPATCH_QUEUE_PRIORITY_HIGH
: 高优先级。
DISPATCH_QUEUE_PRIORITY_DEFAULT
: 默认优先级。
DISPATCH_QUEUE_PRIORITY_LOW
: 低优先级。
DISPATCH_QUEUE_PRIORITY_BACKGROUND
: 后台运行队列。(2).
flags
: 需要传0,是苹果留着以后用的。现在就是传0,如果不传0,可能会返回NULL。
举例 :
ini
// 获取主队列
dispatch_queue_t jd_main_queue = dispatch_get_main_queue();
// 获取全局并发队列
dispatch_queue_t jd_global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3. GCD的任务和函数的创建
任务 :
swift
// GCD中的任务
dispatch_block_t mission_block = ^{
NSLog(@"\n\n这是一个GCD的任务,当前线程 : %@\n",[NSThread currentThread]);
};
函数 :
objectivec
dispatch_async(jd_concurrent_queue, ^{
NSLog(@"这是一个异步函数+一个并发队列,当前线程是 : %@",[NSThread currentThread]);
});
dispatch_sync(jd_serial_queue, ^{
NSLog(@"这是一个同步函数+一个串行队列,当前线程是 : %@",[NSThread currentThread]);
});
三、GCD的函数和队列配合
永远要明确一点,任务(你要做的逻辑)都是放在队列里面的,函数是用来告诉队列怎么执行任务的。无论是同步函数还是异步函数,这两个函数也是任务。
1. 简单的无嵌套
区别 | 串行队列 | 并发队列 | 主队列 |
---|---|---|---|
同步函数 | 不开启新线程, 按顺序执行队列中的任务 | 不开启新线程, 按顺序执行队列中的任务 | 主线程中执行会造成死锁,不执行任务。 其他线程执行则不会出错。 |
异步函数 | 开启1条新线程, 按顺序执行队列中的任务 | 按情况开启新线程, 并发执行队列中的任务 | 不开启新线程, 按顺序执行任务。 |
举例 :
1. 串行队列 + 同步函数 :
代码 :

执行结果 :

串行队列 + 同步函数 : 不开启新的线程,按顺序执行队列中的任务。
2. 串行队列 + 异步函数
代码 :

执行结果 :

串行队列 + 异步函数 : 开启一条新的线程,按顺序执行队列中的任务。
3. 并发队列 + 同步函数
代码 :

执行结果 :

并发队列 + 同步函数 : 不开启新的线程,按顺序执行队列中的任务。
4. 并发队列 + 异步函数
代码 :

执行结果 :

并发队列 + 异步函数 : 按情况开启新的线程,任务执行顺序不一定。
5. 主队列 + 同步函数
代码 :

执行结果 :

主队列 + 同步函数 : 造成死锁。因为同步函数也是一个任务,假设同步函数本身这个任务叫任务A,代码中的mission3(也可以是1或者2)叫任务B。在主队列中,任务A是在任务B的前面,所以先开始执行任务A,任务A中要求同步函数执行任务B,同步函数就会导致任务B不执行完成,别的任务(比如任务A)是不可以执行的,但是任务B在主队列中是在任务A的后面,主队列是不允许任务B先执行完成再执行任务A的,主队列和同步函数打架,一个要先完成任务A,一个因为已经开始执行任务B,必须要先完成任务B才可以给你主队列完成任务A,就造成了死锁。
6. 主队列 + 异步函数
代码 :

执行结果 :

主队列 + 异步函数 : 不开启新线程,按顺序执行任务。
2. 函数嵌套
异步函数 + 并发队列 嵌套(用同一个并发队列) | 同步函数 + 并发队列 嵌套(用同一个并发队列) | 异步函数 + 串行队列 嵌套(用同一个串行队列) | 同步函数 + 串行队列 嵌套(用同一个串行队列) | |
---|---|---|---|---|
同步 | 异步可能开启一个新线程, 嵌套的同步不会开启新线程,在异步开启的新线程中 串行执行任务 | 不开启新线程, 串行执行任务 | 造成死锁 | 造成死锁 |
异步 | 开启新线程, 嵌套的异步函数也会开启新线程, 并发执行任务 | 同步函数不会开启新线程, 异步函数会开启新线程, 并发执行任务 | 异步函数会开启新线程, 串行执行任务 | 异步函数会开启新线程, 串行执行任务 |
举例
1. 异步函数嵌套同步函数 + 同一个并发队列
代码 :

执行结果 :

异步函数嵌套同步函数 + 同一个并发队列 : 异步函数可能会开启新的线程,嵌套的同步函数不会开启新的线程,会在异步函数开启的新线程中串行的执行任务。不同的异步函数可能会开启不同的新线程,不同的异步函数之间,任务的执行是并发的,但是同一个异步函数中嵌套的同步函数,一定是串行的。
2. 异步函数嵌套异步函数 + 同一个并发队列
代码 :

执行结果 :

异步函数本身就会开启新的线程,嵌套的异步函数也可能会开启新的线程,所以任务的执行完全是并发的,没有任何的顺序可言。
3. 同步函数嵌套同步函数 + 同一个并发队列
代码 :

执行结果 :

同步函数本身是不开启新线程的,无论是不是并发队列。因为同步函数本身就有一个特性:同步函数添加到队列的任务,同步函数会保证这个任务执行完成之后,才允许在同步函数之后添加的任务执行,又因为是同步嵌套同步,就导致了哪怕是并发队列,也是串行执行任务。
4. 同步函数嵌套异步函数 + 同一个并发队列
代码 :

执行结果 :

同步函数会保证自己添加的到并发队列的任务完成之后,再让其他的任务执行,同步函数嵌套异步函数,被嵌套的异步函数会单独开启一条线程执行任务。但是一定要等前一个同步函数执行完成,下一个同步函数才可以执行任务。包括下一个同步函数中嵌套的异步函数,也要等到前一个同步函数执行完成任务才可以执行。
5. 异步函数嵌套同步函数 + 同一个串行队列
代码 :

执行结果 :

异步函数嵌套同步函数 + 同一个串行队列会造成死锁。
死锁原因 : 和主队列 + 同步函数一样。因为主队列本身也就是一个特殊的串行队列,对于串行队列来说,所有的任务都是要按照顺序执行的。异步函数先开启了线程执行串行队列中的任务,串行队列中的第1个任务就是NSLog任务4,第2个任务是嵌套同步函数,第3个任务是这个嵌套的同步函数加到串行队列中的mission3,第4个任务是NSLog任务5。当第1个任务执行完成之后,要执行串行队列中的第2个任务,也就是同步函数给串行队列添加mission3,并且因为是同步函数,所以同步函数添加完任务mission3之后,就要执行这个任务mission3,不执行完成的话同步函数是不会让第4个任务
NSLog任务5
执行的,但是同步函数添加的任务mission3在串行队列中是排在同步函数本身这个任务之后的,也就是说,串行队列要求先执行完同步函数这个任务,才给你执行mission3,但是同步函数不同意,同步函数说 : 我执行完成的标准就是完成mission3,串行队列说 : mission3在你自己这个任务后面,不可以比你先执行。于是串行队列和同步函数打架,就造成了死锁。
死锁原因图 :

6. 异步函数嵌套异步函数 + 同一个串行队列
代码 :

执行结果 :

异步函数嵌套异步函数 + 同一个串行队列 : 会因为是串行队列的原因,导致添加到队列的任务串行执行,又因为是异步函数,所以会开启一条新的线程。
为什么嵌套的异步函数不再开启一条新的线程呢?因为是串行队列,就算开启新的线程,依然要根据串行队列的特性,一个一个的执行任务。不可以在前面的任务执行完成之前就执行后面的任务,所以嵌套的异步函数再开启一条新的线程是没有意义的。
为什么嵌套的异步函数添加的任务是在最后才执行的?因为异步函数本身这个任务是在主线程中执行的,异步函数又不会等到任务执行完毕后才结束,异步函数只管开启新线程,并给串行队列添加队伍,新线程什么时候执行串行队列中的任务,对于异步函数来说是不管的。那么主线程就不会直接去执行被嵌套的异步函数添加到串行队列的任务,而是只执行主队列中的任务,也就是外部的三个异步函数给串行队列添加任务。被嵌套的异步函数开启线程又是一个耗时的操作,所以它抢不到新开的线程,只能等待新线程先把由主线程添加到串行队列的任务先执行完,再执行被嵌套的异步函数添加到串行队列的任务。
7. 同步函数嵌套异步函数 + 同一个串行队列
代码 :

执行结果 :
造成死锁。

同步函数嵌套同步函数 + 同一个串行队列 : 会造成死锁。
死锁的原因 : 同步函数是不会开启新的线程的,所以无论是主队列还是自己创建的串行队列中的任务都是由主线程执行的。因为是串行队列,所以任务必须是按照顺序来执行的,前面的任务不完成,后面的任务是不可以执行的。主线程在执行同步函数这个任务的时候,同步函数本身这个任务就是给串行队列添加任务,并要求线程(同步函数没能力开启新线程,又不是被异步函数包裹,所以只能由主线程来执行任务)把这些任务都执行完,才可以执行下面的任务。当主线程开始执行同步函数本身这个任务的时候,就被同步函数要求去执行串行队列中添加的任务,于是主线程就开始执行串行队列中的任务,而不是继续执行主队列中的任务。执行完NSLog任务4,开始执行被嵌套的同步函数,这时候,被嵌套的同步函数又要求主线程要把我的mission3执行完,才可以执行别的任务,但是mission3是添加到串行队列中的,串行队列中,被嵌套的同步函数自己这个任务是在mission3之前的,所以被嵌套的同步函数没有执行完成之前,串行队列是不允许mission3执行的,但是被嵌套的同步函数想要执行完成,就必须完成mission3才算执行完成。于是又造成了被嵌套的同步函数和mission3互相等待,造成了死锁。和上面的两个原因是一样的。
8. 同步函数嵌套异步函数 + 同一个串行队列
代码 :

执行结果 :

同步函数嵌套异步函数 + 同一个串行队列 : 因为是同步函数在外,所以同步函数不会开启新的线程,任务就要由主线程来执行,并且因为是同步函数,所以主线程只能先执行完一个同步函数,才可以执行下一个同步函数。被嵌套的异步函数可以开启线程,但是这是一个耗时操作,所以它添加任务(例如mission3)的速度要比NSLog任务5慢。所以每个同步函数中的第三个NSLog任务都比异步函数添加任务(例如mission3)到串行队列要早。串行队列会按照添加任务的顺序执行任务,被嵌套的异步函数会开启新的线程执行任务。
总结
- GCD是苹果推出的多线程解决方案,是由C编写的函数。
- GCD可以自己管理线程的生命周期,例如 : 线程的创建、线程的调度、线程的阻塞、线程的销毁。
- GCD可以调用CPU的多核心。
- GCD的核心是任务、队列和函数。可以说 GCD = 任务 + 队列(存放任务的) + 函数(执行任务的),将任务放入到队列也是一种任务,所以函数本身也是一种任务。
- GCD中的队列分为 : 串行队列和并发队列。其中有两个特殊的队列,一个是主队列,一个是全局并发队列。
- 串行队列 : 串行队列中的任务必须一个接一个的执行,遵循FIFO原则,先进先出。
- 主队列 : 主队列是一种特殊的串行队列,特殊在主队列中的任务全部在主线程中执行。
- 并发队列 : 并发队列中的任务是一起执行的,谁先执行完成,谁就先输出。执行完顺序不一定。但是并发队列只有在异步函数中才体现能力,因为同步函数不开启新的线程,所以如果用同步函数,实际上任务还是先进先出的,谁先进来,线程就先执行谁。
- 全局并发队列 : 全局并发队列是一种特殊的并发队列,是GCD提供的默认并发队列,想使用并发队列,又没有特殊的需求,可以直接就用全局并发队列,全局并发队列可以设置优先级。
- 同步函数 : 同步函数不具备开辟新线程的能力,同步函数可以添加任务到指定队列中,在同步函数添加的任务没有执行完成之前,同步函数使用的这条线程是不可以执行其他的任务的,其他的任务都要处于等待状态中,直到同步函数添加的任务执行完成,其他的任务才可以执行。
- 异步函数 : 异步函数具备开辟新线程的能力,异步函数也可以添加任务到指定的队列中,但是异步函数只管添加任务和执行任务,不会保证添加的任务一定要马上执行完成,可以等待其他的任务先执行,不会影响到当前线程中其他任务的执行。
- 会造成死锁的情况 : 一般都是串行队列 + 同步函数,因为串行队列遵循FIFO,先进的必先出。同步函数又必须保证自己添加的任务执行完成才可以执行其他的。
(1). 主队列 + 同步函数
(2). 异步函数嵌套同步函数 + 串行队列。
(3). 同步函数嵌套同步函数 + 串行队列。