GCD(Grand Central Dispatch)的使用方法

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值