在上一篇文章多线程中讲了些多线程基础知识,这篇文章以GCD
为例进行深入分析。
1 GCD简介
GCD
,全称Grand Central Dispatch
(中央调度中心),纯C语言
开发,提供了很多强大的函数
1.1 GCD的优势
- GCD是苹果公司为
多核的并行运算
提出的解决方案
- GCD会
自动利用
更多的CPU内核
(比如双核、四核) - GCD会
自动管理线程的生命周期
(创建线程、调度任务、销毁线程)。
程序员只需要告诉GCD
想要执行的任务
,不需要
编写任何线程管理相关代码
(调度、销毁都不用管)
【重点】
: 将任务
添加到队列
,并指定执行任务的函数
1.2 GCD核心
这里用一个简单的实例,来帮助更好的理解任务
+队列
+执行任务的函数
// 任务(block)
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
// 队列(此处串行队列)
dispatch_queue_t queue = dispatch_queue_create("com.lbh.queue", DISPATCH_QUEUE_SERIAL);
// 执行任务的函数(此处异步函数)
dispatch_async(queue, block);
- 使用
dispatch_block_t
创建任务 - 使用
dispatch_queue_t
创建队列 - 将任务添加到队列,并指定执行任务的函数
dispatch_async
任务
是使用block封装
的函数
,没有参数
和返回值
。任务创建好后,等待执行任务的函数
将其放入队列
中
拓展
:执行block,需要调用block(),这步调用,是执行任务的函数内部自动管理。
后面解析dispatch源码时,可以清楚知道调用时机
2 函数与队列
2.1 函数
在GCD中执行任务的方式有两种,同步执行
和异步执行
,分别对应 同步函数dispatch_sync
和 异步函数dispatch_async
。
2.1.1 dispatch_sync 同步函数
这个函数会把一个block加入到指定的队列
中,而且会一直等到执行完blcok,这个函数才返回。因此在block执行完之前
,调用dispatch_sync方法的线程是阻塞的
。
-
必须等待当前语句执行完毕
,才会执行下一条语句 -
不会开启线程
,即不具备开启新线程的能力
2.1.2 dispatch_async 异步函数
这个函数也会把一个block加入到指定的队列
中,但是和同步执行不同的是,这个函数把block加入队列后不等block的执行就立刻返回了。
-
不用等待当前语句执行完毕
,就可以执行下一条语句 - 会
开启线程
执行block任务,即具备开启新线程的能力
(但并不一定开启新线程
,这个与任务所指定的队列类型有关)
2.2 队列
多线程中所说的队列
(Dispatch Queue)是指执行任务的等待队列
,即用来存放任务的队列。队列是一种特殊的线性表,遵循先进先出(FIFO)
原则,即新任务总是被插入到队尾,而任务的读取从队首开始读取。
GCD的队列包含串行队列
和并行队列
两种
2.2.1 串行队列
每次只有一个任务被执行
,等待上一个任务执行完毕再执行下一个,即只开启一个线程
(通俗理解:同一时刻只调度一个任务执行,类似单车道,汽车只能一辆辆排队通过)
使用dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);
创建串行队列,其中的DISPATCH_QUEUE_SERIAL
也可以使用NULL
表示
// 串行队列的获取方法
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.lbh.Queue", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.lbh.Queue", DISPATCH_QUEUE_SERIAL);
2.2.2 并行队列
一次可以并发执行多个任务
,即开启多个线程
,并同时执行任务(通俗理解:同一时刻可以调度多个任务执行,类似多车道,同时可以多辆汽车通过)
使用dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);
创建并发队列
// 并发队列的获取方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lbh.Queue", DISPATCH_QUEUE_CONCURRENT);
2.2.3 主队列
主队列(Main Dispatch Queue),GCD中提供的特殊的串行队列
专门用来在主线程上调度任务的串行队列,依赖于主线程、主Runloop,在main函数调用之前
自动创建
;不会开启线程
如果当前
主线程正在执行任务
,需要等当前任务执行完
,才会继续调度其他任务使用
dispatch_get_main_queue()
获得主队列
//主队列的获取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue();
2.2.4 全局并发队列
全局并发队列(Global Dispatch Queue):GCD提供的默认的并发队列
为了方便程序员的使用,苹果提供了全局队列
在使用
多线程开发
时,如果对队列没有特殊需求
,在执行异步任务
时,可以直接使用全局队列
-
使用
dispatch_get_global_queue
获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
第一个参数表示队列优先级,默认优先级为
DISPATCH_QUEUE_PRIORITY_DEFAULT=0
,在ios9之后,已经被服务质量(quality-of-service)
取代第二个参数使用0
//全局并发队列的获取方法
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
//优先级从高到低(对应的服务质量)依次为
- DISPATCH_QUEUE_PRIORITY_HIGH -- QOS_CLASS_USER_INITIATED
- DISPATCH_QUEUE_PRIORITY_DEFAULT -- QOS_CLASS_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW -- QOS_CLASS_UTILITY
- DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND
2.3 函数与队列的各种组合情况
2.3.1 同步函数 + 串行队列
/**
串行同步队列 : FIFO: 先进先出
*/
- (void)serialSyncTest{
//1:创建串行队列
dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行结果
同步函数会执行完当前任务才继续执行下一个任务,没有开启新线程,是在主线程执行的
分析
问题
:为什么block
里的NSLog
打印是在主线程?
解答
:
⬇️
dispatch_sync
的官方注释里面有这么一句话:
As an optimization, dispatch_sync() invokes the block on the current thread when possible.
作为优化,如果可能,直接在当前线程调用这个block。
所以,一般,在大多数情况下
,通过dispatch_sync添加的任务,在哪个线程添加就会在哪个线程执行
,但在这个案例里 block
仍是在自定义队列中执行,因为整个block
放到主线程中会导致死锁。
dispatch_queue_t queue = dispatch_queue_create("lbh", NULL);
dispatch_async(queue, ^{
NSLog(@"current : %@", [NSThread currentThread]);
dispatch_queue_t serialQueue = dispatch_queue_create("lbh.1", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
// block 1
NSLog(@"current 1: %@", [NSThread currentThread]);
});
dispatch_sync(serialQueue, ^{
// block 2
NSLog(@"current 2: %@", [NSThread currentThread]);
});
});
// 打印结果
// current : {number = 3, name = (null)}
// current 1: {number = 3, name = (null)}
// current 2: {number = 3, name = (null)}
⬆️
2.3.2 同步函数 + 主队列
- (void)mainSyncTest{
NSLog(@"1-%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"-%@",[NSThread currentThread]);
});
}
运行结果
运行崩溃
问题
:为什么会崩溃?
解答
:
实际上是出现死锁
⬇️
解释1:
dispatch_sync
同步函数添加任务,会在当前线程执行,而当前线程就是主线程
,dispatch_sync
由于同步会造成线程阻塞,所以主线程会出现阻塞情况
;
block
任务是在主线程中执行的,而主线程又出现阻塞情况,所以block
一直无法执行,block
无法执行完成又会导致dispatch_sync
同步函数一直执行。
解释2:
我们将上面的例子改一下:
dispatch_queue_t queue = dispatch_queue_create("lbh", NULL);
dispatch_async(queue, ^{
NSLog(@"1-%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"-%@",[NSThread currentThread]);
});
});
// 打印结果
// 1-{number = 5, name = (null)}
// -{number = 1, name = main}
此时dispatch_sync
在子线程中,阻塞的是子线程,对主线程并没有影响
⬆️
2.3.3 异步函数 + 串行队列
/**
串行异步队列
*/
- (void)serialAsyncTest{
//1:创建串行队列
dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行结果
分析
问题
:为什么NSLog(@"hello queue");
执行在前?
解答
:
⬇️
这涉及到耗时
问题,
1 异步函数耗时
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
// testMethod();
});
NSLog(@"= %f",CFAbsoluteTimeGetCurrent()-time);
// 打印结果
// = 0.000022
2 同步函数耗时
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
dispatch_queue_t queue = dispatch_queue_create("com.lgcooci.cn", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
//testMethod();
});
NSLog(@"= %f",CFAbsoluteTimeGetCurrent()-time);
// 打印结果
// = 0.000011
3 主队列耗时
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
NSLog(@"= %f",CFAbsoluteTimeGetCurrent()-time);
// 打印结果
// = 0.000000
在主队列中几乎没有耗时,而同步和异步函数都有一定的耗时,所以NSLog(@"hello queue");
执行在前
⬆️
2.3.4 异步函数 + 主队列
/**
主队列异步
不会开线程 顺序
*/
- (void)mainAsyncTest{
for (int i = 0; i<10; i++) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行结果
异步函数不会等当前任务执行完就会执行下一个任务,没有开启新线程,是在主线程执行的
分析
2.3.5 同步函数 + 并行队列
/**
同步并发 : 堵塞 同步锁 队列 : resume supend 线程 操作, 队列挂起 任务能否执行
*/
- (void)concurrentSyncTest{
//1:创建并发队列
dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行结果
分析
2.3.6 异步函数 + 并发队列
/**
异步并发: 有了异步函数不一定开辟线程
*/
- (void)concurrentAsyncTest{
//1:创建并发队列
dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行结果
乱序执行,开辟了新线程
分析
2.3.7 同步函数 + 全局并发队列
/**
全局同步
全局队列:一个并发队列
*/
- (void)globalSyncTest{
for (int i = 0; i<10; i++) {
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行结果
2.3.8 异步函数 + 全局并发队列
/**
全局异步
全局队列:一个并发队列
*/
- (void)globalAsyncTest{
for (int i = 0; i<10; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行结果
总结
函数\队列 | 串行队列 | 主队列 | 并发队列 | 全局并发队列 |
---|---|---|---|---|
同步函数 | 顺序执行,不开辟线程 | 死锁 | 顺序执行,不开辟线程 | 顺序执行,不开辟线程 |
异步函数 | 顺序执行,开辟线程 | 顺序执行,不开辟线程 | 乱序执行,开辟线程 | 乱序执行,开辟线程 |
3 面试题
3.1 异步函数+并行队列
- (void)interview01{
dispatch_queue_t queue = dispatch_queue_create("lbh", 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 5 2 4 3
打印结果
1
和5
在主线程执行, 两个dispatch_async
异步函数开辟了两个线程 2
、4
在第一个新线程执行 3
在第二个新线程执行。
分析
我们可以以 自定义线程任务为分界 将程序拆成两部分进行分析:
part1
:
dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1- %@",[NSThread currentThread]);
// 耗时
dispatch_async(queue, ^{
});
NSLog(@"5- %@",[NSThread currentThread]);
part2
:
NSLog(@"2- %@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"3- %@",[NSThread currentThread]);
});
NSLog(@"4- %@",[NSThread currentThread]);
综合两部分,最终打印顺序为: 1 5 2 4 3
3.2 异步函数嵌套同步函数 + 并发队列
dispatch_queue_t queue = dispatch_queue_create("lbh", 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 5 2 3 4
运行结果
1和5
在主线程执行, dispatch_async
异步函数开辟了一个线程 ,由于dispatch_sync
同步函数不会开辟新线程,所以2
、3
、4
都是在这个线程中执行。
分析
part1
:同面试题1相同,这里不做讲解
part2
:
NSLog(@"2- %@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"3- %@",[NSThread currentThread]);
});
NSLog(@"4- %@",[NSThread currentThread]);
综合两部分,最终打印顺序为: 1 5 2 3 4
3.3 异步函数嵌套同步函数 + 串行队列
- (void)interview03{
// 同步队列
dispatch_queue_t queue = dispatch_queue_create("lbh", 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
、5
、2
后出现死锁情况
分析
part1
:同面试题1相同,这里不做讲解
part2
:
NSLog(@"2- %@",[NSThread currentThread]);
// 同步
dispatch_sync(queue, ^{
NSLog(@"3- %@",[NSThread currentThread]);
});
NSLog(@"4- %@",[NSThread currentThread]);
这部分是在串行队列中执行
综合两部分,最终打印顺序为: 1 5 2
后出现死锁
3.4 异步函数 + 同步函数 + 并发队列
下面代码的执行顺序是什么?(答案是 AC)
A: 1230789
B: 1237890
C: 3120798
D: 2137890
- (void)interview04{//
dispatch_queue_t queue = dispatch_queue_create("lbh", DISPATCH_QUEUE_CONCURRENT);
// 1 2 3
// 0 (7 8 9)
dispatch_async(queue, ^{ // 耗时
NSLog(@"1-%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-%@",[NSThread currentThread]);
});
// 堵塞哪一行
dispatch_sync(queue, ^{
NSLog(@"3-%@",[NSThread currentThread]);
});
NSLog(@"0-%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"7-%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"8-%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"9-%@",[NSThread currentThread]);
});
}
分析
此题只能用排除法:打印3在打印0之前,全部满足;打印0在打印(7、8、9)之前,可以排除B 和 D ,答案为A、C