1. GCD简介
GCD全称是Grand Central Dispatch纯
C语言,提供例如非常强大的函数GCD作用:将任务添加到队列,并指定任务执行的函数
1.1 GCD优势
GCD是苹果公司为多核的并行运算提出的解决方案GCD会自动利用更多的CPU内核(比如双核、四核)GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)程序员只需要告诉
GCD想要执行什么任务,不需要编写任何线程管理代码
1.2 GCD基础
GCD的基本用法
dispatch_async(dispatch_get_global_queue(0, 0), ^{NSLog(@"%@",[NSThread currentThread]);});
还原最基础的写法,可拆分成任务、队列和函数
- (void)syncTest{// 任务dispatch_block_t block = ^{NSLog(@"hello GCD");};// 串行队列dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);// 函数dispatch_async(queue, block);}
使用
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 串行队列

串行队列:Serial Dispatch Queue
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.lg.serial", NULL);dispatch_queue_t serialQueue2 = dispatch_queue_create("com.lg.serial", DISPATCH_QUEUE_SERIAL);
每次只有一个任务被执行,等待上一个任务执行完毕再执行下一个
只开启一个线程,同一时刻只调度一个任务执行
使用
DISPATCH_QUEUE_SERIAL创建串行队列DISPATCH_QUEUE_SERIAL也可传入NULL,默认创建为串行队列
3.2 并发队列

并发队列:Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lg.concurrent", DISPATCH_QUEUE_CONCURRENT);
一次可以并发执行多个任务
开启多个线程,同一时刻可以调度多个任务执行
使用
DISPATCH_QUEUE_CONCURRENT创建并发队列并发队列的并发功能只有在异步函数下才有效
3.3 主队列
主队列:Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
主队列:
GCD提供的特殊的串行队列专门用来在主线程上调度任务的串行队列,依赖于主线程、主
Runloop,在main函数调用之前自动创建不会开启线程
如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
使用
dispatch_get_main_queue获得主队列通常在返回主线程更新
UI时使用
3.4 全局并发队列
全局并发队列:Global Dispatch Queue
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_HIGH:QOS_CLASS_USER_INITIATEDDISPATCH_QUEUE_PRIORITY_DEFAULT:QOS_CLASS_DEFAULTDISPATCH_QUEUE_PRIORITY_LOW:QOS_CLASS_UTILITYDISPATCH_QUEUE_PRIORITY_BACKGROUND:QOS_CLASS_BACKGROUND
日常开发中,主队列 + 全局并发队列的使用:
dispatch_async(dispatch_get_global_queue(0, 0), ^{//执行耗时操作dispatch_async(dispatch_get_main_queue(), ^{//回到主线程进行UI操作});});
4. 队列与函数的搭配使用
| 队列/函数 | 同步函数 | 异步函数 |
|---|---|---|
| 串行队列 | 不会开启线程,在当前线程执行 任务串行执行,一个接一个 |
开启新线程 任务串行执行,一个接一个 |
| 并发队列 | 不会开启线程,在当前线程执行 任务串行执行,一个接一个 |
开启新线程 任务异步执行,没有顺序,和 CPU调度有关 |
| 主队列 | 死锁 | 不会开启线程,在当前线程执行 任务串行执行,一个接一个 |
| 全局队列 | 不会开启线程,在当前线程执行 任务串行执行,一个接一个 |
开启新线程 任务异步执行,没有顺序,和 CPU调度有关 |
4.1 串行队列
4.1.1 同步函数
// 同步串行队列- (void)serialSyncTest{dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_SERIAL);for (int i = 0; i<5; i++) {dispatch_sync(queue, ^{NSLog(@"%d:%@",i,[NSThread currentThread]);});}NSLog(@"hello queue");}-------------------------//输出以下内容:0:<NSThread: 0x2807e83c0>{number = 1, name = main}1:<NSThread: 0x2807e83c0>{number = 1, name = main}2:<NSThread: 0x2807e83c0>{number = 1, name = main}3:<NSThread: 0x2807e83c0>{number = 1, name = main}4:<NSThread: 0x2807e83c0>{number = 1, name = main}hello queue
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
4.1.2 异步函数
// 异步串行队列- (void)serialAsyncTest{dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_SERIAL);for (int i = 0; i<5; i++) {dispatch_async(queue, ^{NSLog(@"%d:%@",i,[NSThread currentThread]);});}NSLog(@"hello queue");}-------------------------//输出以下内容:hello queue0:<NSThread: 0x282ba5740>{number = 5, name = (null)}1:<NSThread: 0x282ba5740>{number = 5, name = (null)}2:<NSThread: 0x282ba5740>{number = 5, name = (null)}3:<NSThread: 0x282ba5740>{number = 5, name = (null)}4:<NSThread: 0x282ba5740>{number = 5, name = (null)}
- 开启新线程
- 任务串行执行,一个接一个
4.2 并发队列
4.2.1 同步函数
// 同步并发队列- (void)concurrentSyncTest{dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);for (int i = 0; i<5; i++) {dispatch_sync(queue, ^{NSLog(@"%d-%@",i,[NSThread currentThread]);});}NSLog(@"hello queue");}-------------------------//输出以下内容:0:<NSThread: 0x2807e83c0>{number = 1, name = main}1:<NSThread: 0x2807e83c0>{number = 1, name = main}2:<NSThread: 0x2807e83c0>{number = 1, name = main}3:<NSThread: 0x2807e83c0>{number = 1, name = main}4:<NSThread: 0x2807e83c0>{number = 1, name = main}hello queue
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
4.2.2 异步函数
// 异步并发队列- (void)concurrentAsyncTest{dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);for (int i = 0; i<5; i++) {dispatch_async(queue, ^{NSLog(@"%d:%@",i,[NSThread currentThread]);});}NSLog(@"hello queue");}-------------------------//输出以下内容:hello queue1:<NSThread: 0x2801ef300>{number = 10, name = (null)}0:<NSThread: 0x2801ee300>{number = 8, name = (null)}2:<NSThread: 0x2801e9b00>{number = 9, name = (null)}3:<NSThread: 0x2801edf00>{number = 6, name = (null)}4:<NSThread: 0x2801ee7c0>{number = 4, name = (null)}
- 开启新线程
- 任务异步执行,没有顺序,和
CPU调度有关
4.3 主队列
4.3.1 同步函数
// 同步主队列- (void)mainSyncTest{dispatch_queue_t queue = dispatch_get_main_queue();for (int i = 0; i<5; i++) {dispatch_sync(queue, ^{NSLog(@"%d:%@",i,[NSThread currentThread]);});}NSLog(@"hello queue");}-------------------------//程序崩溃:Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1010b4be0)
主队列的作用,专门用来在主线程上调度任务的串行队列
主队列中增加同步函数,导致主线程需要等待同步函数完成后再执行
由于主队列是特殊的串行队列,同步函数需要等待主线程完成后再执行
所以,两个任务相互等待,产生死锁
4.3.2 异步函数
// 异步主队列- (void)mainAsyncTest{dispatch_queue_t queue = dispatch_get_main_queue();for (int i = 0; i<5; i++) {dispatch_async(queue, ^{NSLog(@"%d:%@",i,[NSThread currentThread]);});}NSLog(@"hello queue");}-------------------------//输出以下内容:hello queue0:<NSThread: 0x280ed0200>{number = 1, name = main}1:<NSThread: 0x280ed0200>{number = 1, name = main}2:<NSThread: 0x280ed0200>{number = 1, name = main}3:<NSThread: 0x280ed0200>{number = 1, name = main}4:<NSThread: 0x280ed0200>{number = 1, name = main}
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
4.4 全局队列
4.4.1 同步函数
// 同步全局队列- (void)globalSyncTest{dispatch_queue_t queue = dispatch_get_global_queue(0, 0);for (int i = 0; i<5; i++) {dispatch_sync(queue, ^{NSLog(@"%d:%@",i,[NSThread currentThread]);});}NSLog(@"hello queue");}-------------------------//输出以下内容:0:<NSThread: 0x283a948c0>{number = 1, name = main}1:<NSThread: 0x283a948c0>{number = 1, name = main}2:<NSThread: 0x283a948c0>{number = 1, name = main}3:<NSThread: 0x283a948c0>{number = 1, name = main}4:<NSThread: 0x283a948c0>{number = 1, name = main}hello queue
- 不会开启线程,在当前线程执行
- 任务串行执行,一个接一个
4.4.2 异步函数
// 异步全局队列- (void)globalAsyncTest{dispatch_queue_t queue = dispatch_get_global_queue(0, 0);for (int i = 0; i<5; i++) {dispatch_async(queue, ^{NSLog(@"%d:%@",i,[NSThread currentThread]);});}NSLog(@"hello queue");}-------------------------//输出以下内容:hello queue0:<NSThread: 0x2813099c0>{number = 9, name = (null)}1:<NSThread: 0x281372b80>{number = 4, name = (null)}2:<NSThread: 0x281371e40>{number = 10, name = (null)}3:<NSThread: 0x281372080>{number = 6, name = (null)}4:<NSThread: 0x281372c00>{number = 3, name = (null)}
- 开启新线程
- 任务异步执行,没有顺序,和
CPU调度有关
4.5 线程死锁
所谓线程死锁,是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行
- 函数调用栈中,
_dispatch_sync_f_slow即为死锁的异常
5. 面试题解析
5.1 题目1
- (void)wbinterDemo{dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);NSLog(@"1:%@",[NSThread currentThread]);dispatch_async(queue, ^{NSLog(@"2:%@",[NSThread currentThread]);dispatch_async(queue, ^{NSLog(@"3:%@",[NSThread currentThread]);});NSLog(@"4:%@",[NSThread currentThread]);});NSLog(@"5:%@",[NSThread currentThread]);}-------------------------//输出以下内容:1:<NSThread: 0x2801a4900>{number = 1, name = main}5:<NSThread: 0x2801a4900>{number = 1, name = main}2:<NSThread: 0x2801e0580>{number = 6, name = (null)}4:<NSThread: 0x2801e0580>{number = 6, name = (null)}3:<NSThread: 0x2801e0580>{number = 6, name = (null)}
5.2 题目2
- (void)wbinterDemo{dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);NSLog(@"1:%@",[NSThread currentThread]);dispatch_async(queue, ^{NSLog(@"2:%@",[NSThread currentThread]);dispatch_sync(queue, ^{NSLog(@"3:%@",[NSThread currentThread]);});NSLog(@"4:%@",[NSThread currentThread]);});NSLog(@"5:%@",[NSThread currentThread]);}-------------------------//输出以下内容:1:<NSThread: 0x281864900>{number = 1, name = main}5:<NSThread: 0x281864900>{number = 1, name = main}2:<NSThread: 0x28185ad40>{number = 5, name = (null)}3:<NSThread: 0x28185ad40>{number = 5, name = (null)}4:<NSThread: 0x28185ad40>{number = 5, name = (null)}
5.3 题目3
- (void)wbinterDemo{dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);NSLog(@"1:%@",[NSThread currentThread]);dispatch_async(queue, ^{NSLog(@"2:%@",[NSThread currentThread]);dispatch_sync(queue, ^{NSLog(@"3:%@",[NSThread currentThread]);});NSLog(@"4:%@",[NSThread currentThread]);});NSLog(@"5:%@",[NSThread currentThread]);}-------------------------//输出以下内容:1:<NSThread: 0x281afc900>{number = 1, name = main}5:<NSThread: 0x281afc900>{number = 1, name = main}2:<NSThread: 0x281abdcc0>{number = 6, name = (null)}//程序崩溃:Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1010b4be0)
1和5在主队列,不受影响异步函数先执行
2,但是4要等待同步函数优先执行同步函数的特性“护犊子”,必须自身优先执行,然后才会执行下面的代码。所以异步函数的完成,必须等待同步函数先执行完成
由于使用串行队列,同步函数又需要代码异步函数执行完成后才能执行
所以,两个函数相互等待,产生死锁
5.4 题目4
- (void)wbinterDemo{dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{NSLog(@"1");});dispatch_async(queue, ^{NSLog(@"2");});dispatch_sync(queue, ^{NSLog(@"3");});NSLog(@"0");dispatch_async(queue, ^{NSLog(@"7");});dispatch_async(queue, ^{NSLog(@"8");});dispatch_async(queue, ^{NSLog(@"9");});}
A:1230789B:1237890C:3120798D:2137890
正确答案:A、C
3在同步函数中,0在主线程,所以3一定在0的前面1和2在异步函数中,所以顺序不一定7、8、9一定会在3和0之后,但它们在异步函数中,所以顺序不一定
5.5 题目5
队列的类型有几种?
//串行队列 - Serial Dispatch Queuedispatch_queue_t serialQueue = dispatch_queue_create("com.lg.cn", NULL);//并发队列 - Concurrent Dispatch Queuedispatch_queue_t concurrentQueue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);//主队列 - Main Dispatch Queuedispatch_queue_t mainQueue = dispatch_get_main_queue();//全局并发队列 - Global Dispatch Queuedispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
正确答案:两种
串行队列:
serialQueue、mainQueue并发队列:
concurrentQueue、globalQueue
5.6 题目6
@property (atomic, assign) int num;- (void)wbinterDemo{while (self.num < 5) {dispatch_async(dispatch_get_global_queue(0, 0), ^{self.num++;});}NSLog(@"end : %d",self.num);}
- 正确答案:
>= 5,虽然num使用atomic修饰,但只能保证在自身线程中是安全的。当开启异步多线程,在while停止循环后,可能还会有线程进行num++,所有最终输出的结果大概率会
5.7 题目7
- (void)wbinterDemo{for (int i= 0; i<10000; i++) {dispatch_async(dispatch_get_global_queue(0, 0), ^{self.num++;});}NSLog(@"end : %d",self.num);}
- 正确答案:
<= 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调度有关
- 同步函数
