1.GCD基本概念
- 系统标准的两个队列
//全局队列,一个并行的队列
dispatch_get_global_queue
//主队列,主线程中的唯一队列,一个串行队列
dispatch_get_main_queue
- 自定义队列
//串行队列
dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL)
//并行队列
dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT)
- 同步/异步线程创建
//同步线程
dispatch_sync(..., ^(block))
//异步线程
dispatch_async(..., ^(block))
它们之间的关系:
- | 同步 | 异步 |
---|---|---|
主队列 | 在主线程中执行 | 在主线程中执行 |
串行队列 | 在当前线程中执行 | 新建线程执行 |
并发队列 | 在当前线程中执行 | 新建线程执行 |
2.队列 (Dispatch queue)
2.1 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue;
在应用程序主线程上执行任务,是串行队列
2.2 全局队列
/* 取得全局队列
第一个参数:线程优先级,设为默认即可,个人习惯写0,等同于默认
第二个参数:标记参数,目前没有用,一般传入0
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
全局队列的优先级有四个:
DISPATCH_QUEUE_PRIORITY_LOW //低优先级
DISPATCH_QUEUE_PRIORITY_HIGH //高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT //默认
DISPATCH_QUEUE_PRIORITY_BACKGROUND //后台
全局队列是并行队列
2.3 串行队列 Serial
Serial: 只执行一个任务。Serial queue常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然各自是同步,但serial queue之间是并发执行。
2.4 并行队列 Concurrent
Concurrent: 可以并发的执行多个任务,但执行完成顺序是随机的。系统提供四个全局并发队列,这四个队列有这对应的优先级,用户是不能够创建全局队列的,只能获取。就是上面的四个优先级
并发与并行:
所谓的”并发”, 英文翻译是concurrent。要注意和“并行(parallelism)”的区别
并发指的是一种现象,一种经常出现,无可避免的现象。它描述的是“多个任务同时发生,需要被处理”这一现象。它的侧重点在于“发生”。
比如有很多人排队等待检票,这一现象就可以理解为并发。
并行指的是一种技术,一个同时处理多个任务的技术。它描述了一种能够同时处理多个任务的能力,侧重点在于“运行”。
比如景点开放了多个检票窗口,同一时间内能服务多个游客。这种情况可以理解为并行。
2.5 用户创建自定义队列 user create queue
创建自己定义的队列,可以用dispatch_queue_create函数,函数有两个参数,第一个自定义的队列名,第二个参数是队列类型,默认NULL或者DISPATCH_QUEUE_SERIAL是串行,参数为DISPATCH_QUEUE_CONCURRENT为并行队列。
//串行队列
dispatch_queue_t serialQ = dispatch_queue_create("队列名",DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t serialQ = dispatch_queue_create("队列名",DISPATCH_QUEUE_CONCURRENT);
3. 同步和异步
同步执行: 同步方法就是我们平时调用的哪些方法。因为任何有编程经验的人都知道,比如在第一行调用foo()方法,那么程序运行到第二行的时候,foo方法肯定是执行完了。
异步执行: 所谓的异步,就是允许在执行某一个任务时,函数立刻返回,但是真正要执行的任务稍后完成。
比如我们在点击保存按钮之后,要先把数据写到磁盘,然后更新UI。同步方法就是等到数据保存完再更新UI,而异步则是立刻从保存数据的方法返回并向后执行代码,同时真正用来保存数据的指令将在稍后执行。
/* 同步执行
第一个参数:执行任务的队列:串行、并行、全局、主队列
第二个参数:block任务
*/
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 异步执行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
4. GCD的死锁问题
举个例子:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t main = dispatch_get_main_queue();
dispatch_sync(main, ^{
NSLog(@"Hello");
});
//运行的时候就crash了
}
这段代码就会导致死锁,因为我们目前在主队列中,又将要同步地添加一个block到主队列(串行)中。
理论分析:
我们知道dispatch_sync表示同步的执行任务,也就是说执行dispatch_sync后,当前队列会阻塞。而dispatch_sync中的block如果要在当前队列中执行,就得等待当前队列程执行完成。
在上面这个例子中,主队列在执行dispatch_sync,随后队列中新增一个任务block。因为主队列是同步队列,所以block要等dispatch_sync执行完才能执行,但是dispatch_sync是同步派发,要等block执行完才算是结束。在主队列中的两个任务互相等待,导致了死锁。
解决方案:
其实在通常情况下我们不必要用dispatch_sync,因为dispatch_async能够更好的利用CPU,提升程序运行速度。
只有当我们需要保证队列中的任务必须顺序执行时,才考虑使用dispatch_sync。在使用dispatch_sync的时候应该分析当前处于哪个队列,以及任务会提交到哪个队列。
5. 队列组
5.1 dispatch_group
了解完队列之后,很自然的会有一个想法:我们怎么知道所有任务都已经执行完了呢?
在单个串行队列中,这个不是问题,因为只要把回调block添加到队列末尾即可。
但是对于并行队列,以及多个串行、并行队列混合的情况,就需要使用 dispatch_group
了。
//1.创建队列组
dispatch_group_t group = dispatch_group_create();
//2.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.多次使用队列组的方法执行任务, 只有异步方法
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"group-03 - %@", [NSThread currentThread]);
}
});
//4.都完成后会自动通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 - %@", [NSThread currentThread]);
});
首先我们要通过 dispatch_group_create() 方法生成一个组。
接下来,我们把 dispatch_async 方法换成 dispatch_group_async。这个方法多了一个参数,第一个参数填刚刚创建的分组。
想问 dispatch_sync 对应的分组方法是什么的童鞋面壁思过三秒钟,思考一下 group 出现的目的和 dispatch_sync 的特点。
最后调用 dispatch_group_notify 方法。这个方法表示把第三个参数 block 传入第二个参数队列中去。而且可以保证第三个参数 block 执行时,group 中的所有任务已经全部完成。
5.2 dispatch_group_wait
dispatch_group_wait
方法是一个很有用的方法,它的完整定义如下:
dispatch_group_wait(<#dispatch_group_t _Nonnull group#>, <#dispatch_time_t timeout#>)
第一个参数表示要等待的 group,第二个则表示等待时间。
第二个 dispatch_time_t 类型的参数还有两个特殊值:DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER。
前者表示立刻检查属于这个 group 的任务是否已经完成,后者则表示一直等到属于这个 group 的任务全部完成。
6. dispatch_after方法
dispatch_after只是延时提交block,不是延时立刻执行。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2);
dispatch_after(time, mainQueue, ^{
NSLog(@"%@",[NSThread currentThread]);
});
dispatch_after 方法有三个参数。第一个表示时间,也就是从现在起往后三秒钟。第二、三个参数分别表示提交到哪个队列和要提交的任务。
例子中的dispatch time的参数,可以先看看函数原型:
dispatch_time_t time = dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
第一个参数为DISPATCH_TIME_NOW表示当前。第二个参数的delta表示纳秒,一秒对应的纳秒为1000000000,系统提供了一些宏来简化
#define NSEC_PER_SEC 1000000000ull //每秒有多少纳秒
#define USEC_PER_SEC 1000000ull //每秒有多少毫秒
#define NSEC_PER_USEC 1000ull //每毫秒有多少纳秒
关键词解释:
- NSEC:纳秒。
- USEC:微妙。
- SEC:秒
- PER:每
所以:
- NSEC_PER_SEC,每秒有多少纳秒。
- USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)
- NSEC_PER_USEC,每毫秒有多少纳秒。
这样如果要表示一秒就可以这样写:
dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);
最后一个“USEC_PER_SEC * NSEC_PER_USEC”,翻译过来就是“每秒的毫秒数乘以每毫秒的纳秒数”,也就是“每秒的纳秒数”,所以,延时500毫秒之类的,也就不难了吧~
7.dispatch_suspend和dispatch_resume
dispatch_suspend,dispatch_resume提供了“挂起、恢复”队列的功能,简单来说,就是可以暂停、恢复队列上的任务。但是这里的“挂起”,并不能保证可以立即停止队列上正在运行的block,看如下例子:
dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);
//提交第一个block,延时5秒打印。
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds...");
});
//提交第二个block,也是延时5秒打印
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:5];
NSLog(@"After 5 seconds again...");
});
//延时一秒
NSLog(@"sleep 1 second...");
[NSThread sleepForTimeInterval:1];
//挂起队列
NSLog(@"suspend...");
dispatch_suspend(queue);
//延时10秒
NSLog(@"sleep 10 second...");
[NSThread sleepForTimeInterval:10];
//恢复队列
NSLog(@"resume...");
dispatch_resume(queue);
运行结果如下:
2017-06-09 18:12:53.638 NNN[23112:843628] sleep 1 second...
2017-06-09 18:12:54.639 NNN[23112:843628] suspend...
2017-06-09 18:12:54.640 NNN[23112:843628] sleep 10 second...
2017-06-09 18:12:58.643 NNN[23112:843677] After 5 seconds...
2017-06-09 18:13:04.642 NNN[23112:843628] resume...
2017-06-09 18:13:09.645 NNN[23112:843675] After 5 seconds again...
可知,在dispatch_suspend挂起队列后,第一个block还是在运行,并且正常输出。
结合文档,我们可以得知,dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。
所以下次想暂停正在队列上运行的block时,还是不要用dispatch_suspend了吧~
本文借鉴了以下作者的内容:
ming1016 bestswifter nixzhu