一、基本概念
1、GCD的介绍
GCD是Grand Central Dispatch的简称,是一套基于C语言的底层API。
GCD简化了多线程的实现,有以下优点:
GCD可用于多核的并行运算;
GCD会自动利用更多的CPU内核(比如双核、四核);
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
GCD抽象层次最高,使用也简单,因此,苹果也推荐使用GCD。但是在这里也需要注意的是使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。
2、GCD中两个核心概念:任务和队列
任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。
同步执行(sync
):只能在当前线程中执行任务,不具备开启新线程的能力
异步执行(async
):可以在新的线程中执行任务,具备开启新线程的能力
队列:这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有两种队列:串行队列
和并行队列
。
并行队列(Concurrent Dispatch Queue
):可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务)
并行功能只有在异步(dispatch_async)函数下才有效
串行队列(Serial Dispatch Queue
):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
关于同步异步、串行并行和线程的关系,下面通过一个表格来总结:
由上图我们可以得出我们有3种队列,2种任务执行方式,那么我们就有了6种不同的组合方式。我们接下来将逐一说明。这6种不同的组合方式是 :1.并行队列 + 同步执行
2.并行队列 + 异步执行
3.串行队列 + 同步执行
4.串行队列 + 异步执行
5.主队列 + 同步执行
6.主队列 + 异步执行
二、GCD的基本使用
1、队列的创建方法
1.1、使用dispatch_queue_create
来创建队列
//参数一:表示队列的唯一标识符,可为空
//参数二:用来识别是串行队列还是并行队列
//dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t _Nullable attr);
// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
1.2、使用dispatch_get_global_queue
来创建全局并行队列
//GCD默认提供了全局的并行队列(参数一:队列优先级 参数二:暂时没用)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2、任务的创建方法
// 同步执行任务创建方法
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 这里放任务代码
});
3、并行队列 + 同步执行
- (void) syncConcurrent
{
NSLog(@"begin-- 并行队列 + 同步执行");
dispatch_queue_t queue= dispatch_queue_create("mytest.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 并行队列 + 同步执行");
}
4、并行队列 + 异步执行
- (void) asyncConcurrent
{
NSLog(@"begin-- 并行队列 + 异步执行");
dispatch_queue_t queue= dispatch_queue_create("mytest.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 并行队列 + 异步执行");
}
5、串行队列 + 同步执行
- (void) syncSerial
{
NSLog(@"begin-- 串行队列 + 同步执行");
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 串行队列 + 同步执行");
}
6、串行队列 + 异步执行
- (void)asyncSerial
{
NSLog(@"begin-- 串行队列 + 异步执行");
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 串行队列 + 异步执行");
}
7、主队列 + 同步执行
//造成死锁:main_queue正在处理syncMain ,而syncMain方法中又有同步事件要处理 ,造成相互等待
- (void)syncMain
{
NSLog(@"begin-- 主队列 + 同步执行");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 主队列 + 同步执行");
}
8、主队列 + 异步执行
- (void)asyncMain
{
NSLog(@"begin-- 主队列 + 异步执行");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"end-- 主队列 + 异步执行");
}
三、GCD的其他方法
1、GCD的栅栏方法 (dispatch_barrier_async)
1.1、什么是dispatch_barrier_async函数
毫无疑问,dispatch_barrier_async函数的作用与barrier的意思相同,在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行,该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用。
1.2、dispatch_barrier_async函数的作用
1.实现高效率的数据库访问和文件访问
2.避免数据竞争
- (void) asyncbarrier
{
//同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用
dispatch_queue_t queue = dispatch_queue_create("mytest.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"----1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----2-----%@", [NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"----barrier-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----3-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"----4-----%@", [NSThread currentThread]);
});
}
输出结果:1 2 --> barrier -->3 4 其中12 与 34 由于并行处理先后顺序不定
2、 GCD只执行一次 ( dispatch_once)
我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
3、GCD任务组 ( dispatch_group)
了解完队列之后,很自然的会有一个想法:我们怎么知道所有任务都已经执行完了呢?
在单个串行队列中,这个不是问题,因为只要把回调block添加到队列末尾即可。但是对于并行队列,以及多个串行、并行队列混合的情况,就需要使用dispatch_group了。
这里需要指出dispatch_group_wait方法是一个很有用的方法,它的完整定义如下:
dispatch_group_wait(<#dispatch_group_t _Nonnull group#>, <#dispatch_time_t timeout#>)
第一个参数表示要等待的group,第二个则表示等待时间。返回值表示经过指定的等待时间,属于这个group的任务是否已经全部执行完,如果是则返回0,否则返回非0。
第二个dispatch_time_t类型的参数还有两个特殊值:DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER。
前者表示立刻检查属于这个group的任务是否已经完成,后者则表示一直等到属于这个group的任务全部完成。
dispatch_group_wait代码使用示例
- (void)groupSync
{
dispatch_queue_t disqueue = dispatch_queue_create("mytest.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t disgroup = dispatch_group_create();
dispatch_group_async(disgroup, disqueue, ^{
NSLog(@"任务一完成");
});
dispatch_group_async(disgroup, disqueue, ^{
sleep(9);
NSLog(@"任务二完成");
});
dispatch_group_notify(disgroup, disqueue, ^{
NSLog(@"dispatch_group_notify 执行");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_group_wait(disgroup, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
NSLog(@"dispatch_group_wait 结束");
});
}
由上面的示例可以看出:
向group中放入两个任务(准确讲是将任务加入到了并行队列disqueue中执行,然后队列和group关联当队列上任务执行完毕时group会进行同步),第二个任务会等待8秒所以第一个任务会先完成;会先打印任务一完成再打印任务二完成,当两个任务都完成时dispatch_group_notify中的block会执行;会接着打印dispatch_group_notify 执行;dispatch_group_wait设置了超时时间为5秒所以它会在5秒后停止等待打印dispatch_group_wait 结束(任务二会等待9秒所以它会在任务二完成前打印);
输出结果:任务一完成 --> dispatch_group_wait 结束 --> 任务二完成 --> dispatch_group_notify 执行
有时候我们会有这样的需求:分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们也可以用dispatch_group。dispatch_group通常有两种用法
我们可以先把任务放到队列中,然后将队列放入队列组中。
调用队列组的dispatch_group_notify回到主线程执行操作。具体如下:
用法一:
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//执行1个耗时
NSLog(@"Time_1");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//执行1个耗时
NSLog(@"Time_2");
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//执行1个耗时
NSLog(@"Time_3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//界面刷新
NSLog(@"任务均完成,刷新界面");
});
用法二: 以耗时的网络请求操作为例
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//请求1
[网络请求:{
成功:dispatch_group_leave(group);
失败:dispatch_group_leave(group);
}];
});
dispatch_group_enter;
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//请求2
[网络请求:{
成功:dispatch_group_leave;
失败:dispatch_group_leave;
}];
});
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//请求3
[网络请求:{
成功:dispatch_group_leave(group);
失败:dispatch_group_leave(group);
}];
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//界面刷新
NSLog(@"任务均完成,刷新界面");
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
方法二中需要注意:
使用dispatch_group_enter(group)和dispatch_group_leave(group),这种方式使用更为灵活,enter和leave必须配合使用,有几次enter就要有几次leave,否则group会一直存在。当所有enter的block都leave后,会执行dispatch_group_notify的block。
4、GCD的延时执行方法 (dispatch_after)
当我们需要延迟执行一段代码时,就需要用到
GCD的dispatch_after
方法。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 3秒后异步执行这里的代码
NSLog(@"run-----");
});
5、GCD的快速迭代方法 (dispatch_apply)
比如说遍历0~99这100个数字,通常我们会用for循环遍历,但是GCD给我们提供了快速迭代的方法dispatch_apply,使我们可以同时遍历。for循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以同时遍历多个数字。代码实现如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(100, queue, ^(size_t index) {
NSLog(@"%zd------%@",index, [NSThread currentThread]);
});