1、GCD简介
Grand Central Dispatch (GCD),是苹果推出的多线程解决方案,它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。
2、任务和队列
- 任务:
就是执行操作的意思,在GCD中使用 block 封装。block就是一个提前准备好的代码块,在需要的时候执行 - 队列:
- 串行队列:一个接一个的调度任务
- 并发队列:可以同时调度多个任务
- 主队列: 专门用来在主线程上调度任务的"队列"
主队列不能在其他线程中调度任务!
如果主线程上当前正在有执行的任务,主队列暂时不会调度任务的执行!
注意:主队列不是主线程- 异步任务,会在主线程的方法执行完成后,被调度
- 同步任务,会造成死锁
- 全局队列,系统提供给程序员,方便程序员使用的全局队列
有关服务质量问题,使用以下代码能够做到 iOS7 & iOS8 的适配
dispatch_get_global_queue(0, 0);
- 执行任务的函数
- 同步执行:当前指令不完成,就不会执行下一条指令
- 异步执行:当前指令不完成,同样可以执行下一条指令
异步是多线程的代名词
- 小结:
- 开不开线程,取决于执行任务的函数,同步不开,异步开
- 开几条线程,取决于队列,串行开一条,并发开多条(异步)
3、GCD的基本使用
GCD的基本使用步骤有两步:
1、创建一个队列。
2、将任务添加到队列中,然后系统就会根据任务类型执行任务。(同步执行或异步执行)
串形队列+同步执行
不会开线程,会顺序执行
// 1. 队列
/**
参数
1. 队列的名称
2. 队列的属性
DISPATCH_QUEUE_SERIAL(NULL) 表示串行
*/
dispatch_queue_t q = dispatch_queue_create("com.chenhuan.queue", DISPATCH_QUEUE_SERIAL);
NSLog(@"start");
// 2. 执行任务
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"end");
输出结果
2017-07-29 19:08:27.030 Demo-GCD[942:62206] start
2017-07-29 19:08:27.030 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 0
2017-07-29 19:08:27.031 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 1
2017-07-29 19:08:27.031 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 2
2017-07-29 19:08:27.031 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 3
2017-07-29 19:08:27.031 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 4
2017-07-29 19:08:27.031 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 5
2017-07-29 19:08:27.032 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 6
2017-07-29 19:08:27.032 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 7
2017-07-29 19:08:27.032 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 8
2017-07-29 19:08:27.032 Demo-GCD[942:62206] <NSThread: 0x61800007eac0>{number = 1, name = main} 9
2017-07-29 19:08:27.032 Demo-GCD[942:62206] end
- 在
串行队列 + 同步执行
可以看到,所有任务都是在主线程中执行的,并没有开启新的线程。而且由于串行队列,所以按顺序一个一个执行。 - 所有任务都在打印的
start
和end
之间,这说明任务是添加到队列中马上执行的。
串形队列+异步执行
会开启新的线程,一个一个顺序执行
dispatch_queue_t q = dispatch_queue_create("com.chenhuan.queue", NULL);
NSLog(@"start");
for (int i = 0; i<10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"end");
输出结果
2017-07-29 19:40:35.466 08-GCD演练[988:72561] start
2017-07-29 19:40:35.466 08-GCD演练[988:72561] end
2017-07-29 19:40:35.466 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 0
2017-07-29 19:40:35.467 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 1
2017-07-29 19:40:35.467 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 2
2017-07-29 19:40:35.467 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 3
2017-07-29 19:40:35.467 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 4
2017-07-29 19:40:35.467 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 5
2017-07-29 19:40:35.468 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 6
2017-07-29 19:40:35.468 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 7
2017-07-29 19:40:35.468 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 8
2017-07-29 19:40:35.468 08-GCD演练[988:72613] <NSThread: 0x610000072b00>{number = 3, name = (null)} 9
- 在
串形队列+异步执行
可以看到,会开启一条新的线程,并且一个一个执行 - 所有任务是在打印的
start
和end
之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。
并发队列+同步执行
不会开启线程,并且顺序执行
// 1. 队列
dispatch_queue_t q = dispatch_queue_create("com.chenhuan.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"start");
// 2. 异步执行
for (int i = 0; i<10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"end");
输出结果
2017-07-29 20:38:10.211 Demo-GCD[1125:109556] start
2017-07-29 20:38:10.212 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 0
2017-07-29 20:38:10.212 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 1
2017-07-29 20:38:10.212 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 2
2017-07-29 20:38:10.212 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 3
2017-07-29 20:38:10.213 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 4
2017-07-29 20:38:10.213 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 5
2017-07-29 20:38:10.213 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 6
2017-07-29 20:38:10.213 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 7
2017-07-29 20:38:10.213 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 8
2017-07-29 20:38:10.214 Demo-GCD[1125:109556] <NSThread: 0x60800007a9c0>{number = 1, name = main} 9
2017-07-29 20:38:10.214 Demo-GCD[1125:109556] end
- 在
并发队列 + 同步执行
可以看到,所有任务都是在主线程中执行的,并没有开启新的线程。而且都是按顺序一个一个执行。 - 所有任务都在打印的
start
和end
之间,这说明任务是添加到队列中马上执行的。
并发队列+异步执行
会开启多条线程,并且交替执行
// 1. 队列
dispatch_queue_t q = dispatch_queue_create("com.chenhuan.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"start");
// 2. 异步执行
for (int i = 0; i<10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"end");
输出结果
2017-07-29 20:36:52.573 Demo-GCD[1100:104147] start
2017-07-29 20:36:52.573 Demo-GCD[1100:104147] end
2017-07-29 20:36:52.573 Demo-GCD[1100:107782] <NSThread: 0x610000264e00>{number = 20, name = (null)} 0
2017-07-29 20:36:52.573 Demo-GCD[1100:108095] <NSThread: 0x618000262b40>{number = 39, name = (null)} 1
2017-07-29 20:36:52.574 Demo-GCD[1100:108096] <NSThread: 0x610000264f40>{number = 40, name = (null)} 2
2017-07-29 20:36:52.574 Demo-GCD[1100:108097] <NSThread: 0x60800026a200>{number = 41, name = (null)} 3
2017-07-29 20:36:52.574 Demo-GCD[1100:108099] <NSThread: 0x60800026a300>{number = 42, name = (null)} 4
2017-07-29 20:36:52.574 Demo-GCD[1100:108102] <NSThread: 0x610000266c40>{number = 43, name = (null)} 5
2017-07-29 20:36:52.574 Demo-GCD[1100:108100] <NSThread: 0x600000264d80>{number = 45, name = (null)} 7
2017-07-29 20:36:52.574 Demo-GCD[1100:108104] <NSThread: 0x600000264c00>{number = 44, name = (null)} 6
2017-07-29 20:36:52.574 Demo-GCD[1100:108106] <NSThread: 0x60800026a280>{number = 46, name = (null)} 8
2017-07-29 20:36:52.574 Demo-GCD[1100:108107] <NSThread: 0x60800026a2c0>{number = 47, name = (null)} 9
- 在
并发队列 + 异步执行
可以看到,所有任务都是在异步线程中执行。并且任务是交替着同时执行的。 - 所有任务都在打印的
start
和end
之间,这说明任务是添加到队列中马上执行的。
主队列+同步执行
不要这样干,会造成死锁
dispatch_queue_t q = dispatch_get_main_queue();
NSLog(@"卡死了吗?");
dispatch_sync(q, ^{
NSLog(@"我来了");
});
NSLog(@"come here");
输出结果
2017-07-29 21:13:23.550 Demo-GCD[1148:120053] 卡死了吗?
- 如果你自己运行以上代码,会发现程序不能运行了。这是因为照成了死锁
- 我们知道
dispatch_sync
表示同步的执行任务,也就是说执行dispatch_sync
后,当前队列会阻塞。而dispatch_sync
中的block
如果要在当前队列中执行,就得等待当前队列程执行完成。 - 在上面这个例子中,主队列在执行
dispatch_sync
,随后队列中新增一个任务block
。因为主队列是同步队列,所以block
要等dispatch_sync
执行完才能执行,但是dispatch_sync
是同步派发,要等block
执行完才算是结束。在主队列中的两个任务互相等待,导致了死锁。
主队列+异步执行
// 1. 主队列 - 程序启动之后已经存在主线程,主队列同样存在
dispatch_queue_t q = dispatch_get_main_queue();
// 2. 安排一个任务
for (int i = 0; i<10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"睡会");
[NSThread sleepForTimeInterval:2.0];
NSLog(@"come here");
输出结果
2017-07-29 21:21:30.547 Demo-GCD[1208:127513] 睡会
2017-07-29 21:21:32.548 Demo-GCD[1208:127513] come here
2017-07-29 21:21:32.548 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 0
2017-07-29 21:21:32.548 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 1
2017-07-29 21:21:32.549 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 2
2017-07-29 21:21:32.549 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 3
2017-07-29 21:21:32.549 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 4
2017-07-29 21:21:32.550 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 5
2017-07-29 21:21:32.550 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 6
2017-07-29 21:21:32.550 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 7
2017-07-29 21:21:32.550 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 8
2017-07-29 21:21:32.551 Demo-GCD[1208:127513] <NSThread: 0x600000076d40>{number = 1, name = main} 9
- 不开线程,顺序执行
- 从上面可以看出,异步执行不会阻塞线程
全局队列
/**
参数
1. 涉及到系统适配
iOS 8 服务质量(让线程响应的更快还是更慢)
- QOS_CLASS_USER_INTERACTIVE 用户交互(用户迫切希望线程快点被执行,不要用耗时的操作)
- QOS_CLASS_USER_INITIATED 用户需要的(同样不要使用耗时操作)
- QOS_CLASS_DEFAULT 默认的(给系统用来重置队列的)
** QOS_CLASS_UTILITY 实用工具(用来做耗时操作)
- QOS_CLASS_BACKGROUND 后台
- QOS_CLASS_UNSPECIFIED 没有指定优先级
iOS 7 调度的优先级
- DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
- DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
- DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
- DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台优先级
提示:尤其不要选择 BACKGROUND 优先级和服务质量,用户不需要知道线程什么时候执行完成!线程的执行会慢的令人发指!
有关服务质量的介绍,用在与 XPC 框架结合使用的,XPC 用在 MAC 平台上做进程间通讯的框架!
因为大家工作后,暂时会考虑 iOS7 & iOS8 的适配,无法使用服务质量,直接指定 0,能够做到 iOS7 & 8 的适配
dispatch_get_global_queue(0, 0);
2. 为未来使用保留的,应该始终传入0
*/
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"com here");
- 全局队列可以当作并发队列来使用。一般我们在开发过程中都是使用
global_queue
4、GCD的其他一些用法
dispatch_group
在实际开发中,有的时候,会需要同时监听多个异步任务最终完成的情况!这个时候就需要用到我们的 dispatch_group
// 1. 队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 2. 调度组
dispatch_group_t group = dispatch_group_create();
// 3. 添加任务,让队列调度,指定任务执行函数,最终通知群组
dispatch_group_async(group, queue, ^{
NSLog(@"download A %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download B %@", [NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:0.8];
NSLog(@"download C %@", [NSThread currentThread]);
});
// 4. 所有任务执行完毕后,获得通知
// 用一个调度组,可以监听全局队列调度的任务,执行完毕后,在主队列执行最终处理!
// dispatch_group_notify 本身是异步的
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 更新UI,通知用户!
NSLog(@"OK %@", [NSThread currentThread]);
});
NSLog(@"come here");
输出结果
2017-07-29 21:31:18.362 Demo-GCD[1255:135674] come here
2017-07-29 21:31:18.362 Demo-GCD[1255:135726] download A <NSThread: 0x60000006e040>{number = 3, name = (null)}
2017-07-29 21:31:19.166 Demo-GCD[1255:135723] download C <NSThread: 0x60000006b200>{number = 4, name = (null)}
2017-07-29 21:31:19.362 Demo-GCD[1255:135741] download B <NSThread: 0x60000006fd80>{number = 5, name = (null)}
2017-07-29 21:31:19.363 Demo-GCD[1255:135674] OK <NSThread: 0x60800006c540>{number = 1, name = main}
首先我们要通过 dispatch_group_create()
方法生成一个组。
接下来,我们把 dispatch_async
方法换成 dispatch_group_async
。这个方法多了一个参数,第一个参数填刚刚创建的分组。
最后调用 dispatch_group_notify
方法。这个方法表示把第三个参数 block 传入第二个参数队列中去。而且可以保证第三个参数 block
执行时,group
中的所有任务已经全部完成。
dispatch_group 第二种调用方式
// 1. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 调度组
dispatch_group_t g = dispatch_group_create();
// 3. 进入群组,执行此函数后,再添加的异步执行的block,会被group监听
// dispatch_group_enter & dispatch_group_leave一定要配对出现
dispatch_group_enter(g);
// 4. 添加任务
dispatch_async(q, ^{
[NSThread sleepForTimeInterval:10.0];
NSLog(@"download A");
// 异步任务中,所有的代码执行完毕后,最后离开群组
dispatch_group_leave(g);
});
// 再次添加任务
dispatch_group_enter(g);
// 5. 添加任务 B
dispatch_async(q, ^{
NSLog(@"download B");
// 异步任务中,所有的代码执行完毕后,最后离开群组
dispatch_group_leave(g);
});
// 6. 拦截通知
// dispatch_group_notify(g, q, ^{
// NSLog(@"Over");
// });
// 等待到永远,死等,阻塞住线程执行,一直到所有的任务执行完毕,才会执行后续的代码!
long time = dispatch_group_wait(g, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)));
NSLog(@"result=%ld",time);
NSLog(@"come here");
输出结果
2017-07-29 21:52:08.123 Demo-GCD[1326:154377] download B
2017-07-29 21:52:10.124 Demo-GCD[1326:154323] result=49
2017-07-29 21:52:10.124 Demo-GCD[1326:154323] come here
2017-07-29 21:52:18.126 Demo-GCD[1326:154391] download A
dispatch_group_wait
方法是一个很有用的方法,它的完整定义如下:
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
第一个参数表示要等待的 group,第二个则表示等待时间。返回值表示经过指定的等待时间,属于这个 group 的任务是否已经全部执行完,如果是则返回 0,否则返回非 0。
第二个 dispatch_time_t 类型的参数还有两个特殊值:DISPATCH_TIME_NOW
和 DISPATCH_TIME_FOREVER
。
前者表示立刻检查属于这个 group
的任务是否已经完成,后者则表示一直等到属于这个 group
的任务全部完成。
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC))
传入这个参数表示等待两秒后检查执行情况。显然没有执行完成 result=49
。
dispatch_after
通过 GCD 还可以进行简单的定时操作,比如在 3 秒后执行某个 block 。代码如下:
NSLog(@"come here");
/**
参数:
从现在开始,经过多少纳秒之后,让 queue 队列,调度 block 任务,异步执行!
1. when
2. queue
3. block
// 从现在开始,经过多少纳秒之后
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
// 主队列
// dispatch_after(when, dispatch_get_main_queue(), ^{
// NSLog(@"%@", [NSThread currentThread]);
// });
// 全局队列
// dispatch_after(when, dispatch_get_global_queue(0, 0), ^{
// NSLog(@"%@", [NSThread currentThread]);
// });
// 串行队列
dispatch_after(when, dispatch_queue_create("com.chenhuan.queue", NULL), ^{
NSLog(@"%@", [NSThread currentThread]);
});
dispatch_after
方法有三个参数。第一个表示时间,也就是从现在起往后三秒钟。第二、三个参数分别表示要提交的任务和提交到哪个队列。
dispatch_once
dispathc_once
函数可以确保某个 block 在应用程序执行的过程中只被处理一次,而且它是线程安全的。所以单例模式可以很简单的实现,以 OC 中单例类为例
+ (instancetype)sharedInstance {
static id instance = nil;
static dispatch_once_t once;
dispatch_once($once, ^{
instance = [[self alloc] init];
});
return sharedManagerInstance;
}