本文首发于我的个人博客: 不羁阁
文章链接: 传送门
本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法这大概是史上最详细清晰的关于 GCD 的详细讲解 + 总结的文章了通过本文, 您将了解到:
GCD 简介
GCD 任务和队列
GCD 的使用步骤
GCD 的基本使用(6 种不同组合区别)
GCD 线程间的通信
GCD 的其他方法(栅栏方法: dispatch_barrier_async 延时执行方法: dispatch_after 一次性代码(只执行一次):dispatch_once 快速迭代方法: dispatch_apply 队列组: dispatch_group 信号量: dispatch_semaphore) 文中 Demo 我已放在了 Github 上, Demo 链接: 传送门
1. GCD 简介
什么是 GCD 呢? 我们先来看看百度百科的解释简单了解下概念
引自百度百科 Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统它是一个在线程池模式的基础上执行的并发任务在 Mac OS X 10.6 雪豹中首次推出, 也可在 iOS 4 及以上版本使用
为什么要用 GCD 呢?
因为 GCD 有很多好处啊, 具体如下:
GCD 可用于多核的并行运算
GCD 会自动利用更多的 CPU 内核(比如双核四核)
GCD 会自动管理线程的生命周期(创建线程调度任务销毁线程)
程序员只需要告诉 GCD 想要执行什么任务, 不需要编写任何线程管理代码
既然 GCD 有这么多的好处, 那么下面我们就来系统的学习一下 GCD 的使用方法
2. GCD 任务和队列
学习 GCD 之前, 先来了解 GCD 中两个核心概念: 任务和队列
任务: 就是执行操作的意思, 换句话说就是你在线程中执行的那段代码在 GCD 中是放在 block 中的执行任务有两种方式: 同步执行 (sync) 和异步执行 (async) 两者的主要区别是: 是否等待队列的任务执行结束, 以及是否具备开启新线程的能力
同步执行(sync):
同步添加任务到指定的队列中, 在添加的任务执行结束之前, 会一直等待, 直到队列里面的任务完成之后再继续执行
只能在当前线程中执行任务, 不具备开启新线程的能力
异步执行(async):
异步添加任务到指定的队列中, 它不会做任何等待, 可以继续执行任务
可以在新的线程中执行任务, 具备开启新线程的能力
举个简单例子: 你要打电话给小明和小白 同步执行就是, 你打电话给小明的时候, 不能同时打给小白, 等到给小明打完了, 才能打给小白 (等待任务执行结束) 而且只能用当前的电话 (不具备开启新线程的能力) 而异步执行就是, 你打电话给小明的时候, 不等和小明通话结束, 还能直接给小白打电话, 不用等着和小明通话结束再打(不用等待任务执行结束) 除了当前电话, 你还可以使用其他所能使用的电话(具备开启新线程的能力)
注意: 异步执行(async) 虽然具有开启新线程的能力, 但是并不一定开启新线程这跟任务所指定的队列类型有关(下面会讲)
队列 (Dispatch Queue): 这里的队列指执行任务的等待队列, 即用来存放任务的队列队列是一种特殊的线性表, 采用 FIFO(先进先出) 的原则, 即新任务总是被插入到队列的末尾, 而读取任务的时候总是从队列的头部开始读取每读取一个任务, 则从队列中释放一个任务队列的结构可参考下图:
在 GCD 中有两种队列: 串行队列和并发队列两者都符合 FIFO(先进先出)的原则两者的主要区别是: 执行顺序不同, 以及开启线程数不同
串行队列(Serial Dispatch Queue):
每次只有一个任务被执行让任务一个接着一个地执行(只开启一个线程, 一个任务执行完毕后, 再执行下一个任务)
并发队列(Concurrent Dispatch Queue):
可以让多个任务并发 (同时) 执行(可以开启多个线程, 并且同时执行任务)
注意: 并发队列 的并发功能只有在异步 (dispatch_async) 函数下才有效
两者具体区别如下两图所示
3. GCD 的使用步骤
GCD 的使用步骤其实很简单, 只有两步
创建一个队列(串行队列或并发队列)
将任务追加到任务的等待队列中, 然后系统就会根据任务类型执行任务(同步执行或异步执行)
下边来看看 队列的创建方法 / 获取方法, 以及 任务的创建方法
3.1 队列的创建方法 / 获取方法
可以使用
dispatch_queue_create
来创建队列, 需要传入两个参数, 第一个参数表示队列的唯一标识符, 用于 DEBUG, 可为空, Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名; 第二个参数用来识别是串行队列还是并发队列
DISPATCH_QUEUE_SERIAL
表示串行队列,
DISPATCH_QUEUE_CONCURRENT
表示并发队列
- // 串行队列的创建方法
- dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
- // 并发队列的创建方法
- dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
对于串行队列, GCD 提供了的一种特殊的串行队列: 主队列(Main Dispatch Queue)
所有放在主队列中的任务, 都会放到主线程中执行
可使用
dispatch_get_main_queue()
获得主队列
- // 主队列的获取方法
- dispatch_queue_t queue = dispatch_get_main_queue();
对于并发队列, GCD 默认提供了全局并发队列(Global Dispatch Queue)
可以使用
dispatch_get_global_queue
来获取需要传入两个参数第一个参数表示队列优先级, 一般用
DISPATCH_QUEUE_PRIORITY_DEFAULT
第二个参数暂时没用, 用 0 即可
- // 全局并发队列的获取方法
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3.2 任务的创建方法
GCD 提供了同步执行任务的创建方法 dispatch_sync 和异步执行任务创建方法 dispatch_async
- // 同步执行任务创建方法
- dispatch_sync(queue, ^{
- // 这里放同步执行任务代码
- });
- // 异步执行任务创建方法
- dispatch_async(queue, ^{
- // 这里放异步执行任务代码
- });
虽然使用 GCD 只需两步, 但是既然我们有两种队列(串行队列 / 并发队列), 两种任务执行方式(同步执行 / 异步执行), 那么我们就有了四种不同的组合方式这四种不同的组合方式是:
同步执行 + 并发队列
异步执行 + 并发队列
同步执行 + 串行队列
异步执行 + 串行队列
实际上, 刚才还说了两种特殊队列: 全局并发队列主队列全局并发队列可以作为普通并发队列来使用但是主队列因为有点特殊, 所以我们就又多了两种组合方式这样就有六种不同的组合方式了
同步执行 + 主队列
异步执行 + 主队列
那么这几种不同组合方式各有什么区别呢, 这里为了方便, 先上结果, 再来讲解你可以直接查看表格结果, 然后跳过 4. GCD 的基本使用
区别 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步 (sync) | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 | 没有开启新线程,串行执行任务 |
异步 (async) | 有开启新线程,并发执行任务 | 有开启新线程 (1 条),串行执行任务 | 没有开启新线程,串行执行任务 |
下边我们来分别讲讲这几种不同的组合方式的使用方法
4. GCD 的基本使用
先来讲讲并发队列的两种执行方式
4.1 同步执行 + 并发队列
在当前线程中执行任务, 不会开启新线程, 执行完一个任务, 再执行下一个任务
- /**
- * 同步执行 + 并发队列
- * 特点: 在当前线程中执行任务, 不会开启新线程, 执行完一个任务, 再执行下一个任务
- */
- - (void)syncConcurrent {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"syncConcurrent---begin");
- dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
- dispatch_sync(queue, ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_sync(queue, ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_sync(queue, ^{
- // 追加任务 3
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- NSLog(@"syncConcurrent---end");
- }
输出结果:
- 2018-02-23 20:34:55.095932+0800 YSC-GCD-demo[19892:4996930] currentThread---<NSThread: 0x60400006bbc0>{number = 1, name = main}
- 2018-02-23 20:34:55.096086+0800 YSC-GCD-demo[19892:4996930] syncConcurrent---begin
- 2018-02-23 20:34:57.097589+0800 YSC-GCD-demo[19892:4996930] 1---<NSThread: 0x60400006bbc0>{number = 1, name = main}
- 2018-02-23 20:34:59.099100+0800 YSC-GCD-demo[19892:4996930] 1---<NSThread: 0x60400006bbc0>{number = 1, name = main}
- 2018-02-23 20:35:01.099843+0800 YSC-GCD-demo[19892:4996930] 2---<NSThread: 0x60400006bbc0>{number = 1, name = main}
- 2018-02-23 20:35:03.101171+0800 YSC-GCD-demo[19892:4996930] 2---<NSThread: 0x60400006bbc0>{number = 1, name = main}
- 2018-02-23 20:35:05.101750+0800 YSC-GCD-demo[19892:4996930] 3---<NSThread: 0x60400006bbc0>{number = 1, name = main}
- 2018-02-23 20:35:07.102414+0800 YSC-GCD-demo[19892:4996930] 3---<NSThread: 0x60400006bbc0>{number = 1, name = main}
- 2018-02-23 20:35:07.102575+0800 YSC-GCD-demo[19892:4996930] syncConcurrent---end
从
同步执行 + 并发队列
中可看到:
所有任务都是在当前线程 (主线程) 中执行的, 没有开启新的线程(同步执行不具备开启新线程的能力)
所有任务都在打印的
syncConcurrent---begin
和
syncConcurrent---end
之间执行的(同步任务需要等待队列的任务执行结束)
任务按顺序执行的按顺序执行的原因: 虽然并发队列可以开启多个线程, 并且同时执行多个任务但是因为本身不能创建新线程, 只有当前线程这一个线程 (同步任务不具备开启新线程的能力), 所以也就不存在并发而且当前线程只有等待当前队列中正在执行的任务执行完毕之后, 才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束) 所以任务只能一个接一个按顺序执行, 不能同时被执行
4.2 异步执行 + 并发队列
可以开启多个线程, 任务交替 (同时) 执行
- /**
- * 异步执行 + 并发队列
- * 特点: 可以开启多个线程, 任务交替 (同时) 执行
- */
- - (void)asyncConcurrent {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"asyncConcurrent---begin");
- dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
- dispatch_async(queue, ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_async(queue, ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_async(queue, ^{
- // 追加任务 3
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- NSLog(@"asyncConcurrent---end");
- }
输出结果:
- 2018-02-23 20:36:41.769269+0800 YSC-GCD-demo[19929:5005237] currentThread---<NSThread: 0x604000062d80>{number = 1, name = main}
- 2018-02-23 20:36:41.769496+0800 YSC-GCD-demo[19929:5005237] asyncConcurrent---begin
- 2018-02-23 20:36:41.769725+0800 YSC-GCD-demo[19929:5005237] asyncConcurrent---end
- 2018-02-23 20:36:43.774442+0800 YSC-GCD-demo[19929:5005566] 2---<NSThread: 0x604000266f00>{number = 5, name = (null)}
- 2018-02-23 20:36:43.774440+0800 YSC-GCD-demo[19929:5005567] 3---<NSThread: 0x60000026f200>{number = 4, name = (null)}
- 2018-02-23 20:36:43.774440+0800 YSC-GCD-demo[19929:5005565] 1---<NSThread: 0x600000264800>{number = 3, name = (null)}
- 2018-02-23 20:36:45.779286+0800 YSC-GCD-demo[19929:5005567] 3---<NSThread: 0x60000026f200>{number = 4, name = (null)}
- 2018-02-23 20:36:45.779302+0800 YSC-GCD-demo[19929:5005565] 1---<NSThread: 0x600000264800>{number = 3, name = (null)}
- 2018-02-23 20:36:45.779286+0800 YSC-GCD-demo[19929:5005566] 2---<NSThread: 0x604000266f00>{number = 5, name = (null)}
在
异步执行 + 并发队列
中可以看出:
除了当前线程(主线程), 系统又开启了 3 个线程, 并且任务是交替 / 同时执行的(异步执行具备开启新线程的能力且并发队列可开启多个线程, 同时执行多个任务)
所有任务是在打印的
syncConcurrent---begin
和
syncConcurrent---end
之后才执行的说明当前线程没有等待, 而是直接开启了新线程, 在新线程中执行任务(异步执行不做等待, 可以继续执行任务)
接下来再来讲讲串行队列的两种执行方式
4.3 同步执行 + 串行队列
不会开启新线程, 在当前线程执行任务任务是串行的, 执行完一个任务, 再执行下一个任务
- /**
- * 同步执行 + 串行队列
- * 特点: 不会开启新线程, 在当前线程执行任务任务是串行的, 执行完一个任务, 再执行下一个任务
- */
- - (void)syncSerial {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"syncSerial---begin");
- dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
- dispatch_sync(queue, ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_sync(queue, ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_sync(queue, ^{
- // 追加任务 3
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- NSLog(@"syncSerial---end");
- }
输出结果:
- 2018-02-23 20:39:37.876811+0800 YSC-GCD-demo[19975:5017162] currentThread---<NSThread: 0x604000079400>{number = 1, name = main}
- 2018-02-23 20:39:37.876998+0800 YSC-GCD-demo[19975:5017162] syncSerial---begin
- 2018-02-23 20:39:39.878316+0800 YSC-GCD-demo[19975:5017162] 1---<NSThread: 0x604000079400>{number = 1, name = main}
- 2018-02-23 20:39:41.879829+0800 YSC-GCD-demo[19975:5017162] 1---<NSThread: 0x604000079400>{number = 1, name = main}
- 2018-02-23 20:39:43.880660+0800 YSC-GCD-demo[19975:5017162] 2---<NSThread: 0x604000079400>{number = 1, name = main}
- 2018-02-23 20:39:45.881265+0800 YSC-GCD-demo[19975:5017162] 2---<NSThread: 0x604000079400>{number = 1, name = main}
- 2018-02-23 20:39:47.882257+0800 YSC-GCD-demo[19975:5017162] 3---<NSThread: 0x604000079400>{number = 1, name = main}
- 2018-02-23 20:39:49.883008+0800 YSC-GCD-demo[19975:5017162] 3---<NSThread: 0x604000079400>{number = 1, name = main}
- 2018-02-23 20:39:49.883253+0800 YSC-GCD-demo[19975:5017162] syncSerial---end
在
同步执行 + 串行队列
可以看到:
所有任务都是在当前线程 (主线程) 中执行的, 并没有开启新的线程(同步执行不具备开启新线程的能力)
所有任务都在打印的
syncConcurrent---begin
和
syncConcurrent---end
之间执行(同步任务需要等待队列的任务执行结束)
任务是按顺序执行的(串行队列每次只有一个任务被执行, 任务一个接一个按顺序执行)
4.4 异步执行 + 串行队列
会开启新线程, 但是因为任务是串行的, 执行完一个任务, 再执行下一个任务
- /**
- * 异步执行 + 串行队列
- * 特点: 会开启新线程, 但是因为任务是串行的, 执行完一个任务, 再执行下一个任务
- */
- - (void)asyncSerial {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"asyncSerial---begin");
- dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
- dispatch_async(queue, ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_async(queue, ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_async(queue, ^{
- // 追加任务 3
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- NSLog(@"asyncSerial---end");
- }
输出结果:
- 2018-02-23 20:41:17.029999+0800 YSC-GCD-demo[20008:5024757] currentThread---<NSThread: 0x604000070440>{number = 1, name = main}
- 2018-02-23 20:41:17.030212+0800 YSC-GCD-demo[20008:5024757] asyncSerial---begin
- 2018-02-23 20:41:17.030364+0800 YSC-GCD-demo[20008:5024757] asyncSerial---end
- 2018-02-23 20:41:19.035379+0800 YSC-GCD-demo[20008:5024950] 1---<NSThread: 0x60000026e100>{number = 3, name = (null)}
- 2018-02-23 20:41:21.037140+0800 YSC-GCD-demo[20008:5024950] 1---<NSThread: 0x60000026e100>{number = 3, name = (null)}
- 2018-02-23 20:41:23.042220+0800 YSC-GCD-demo[20008:5024950] 2---<NSThread: 0x60000026e100>{number = 3, name = (null)}
- 2018-02-23 20:41:25.042971+0800 YSC-GCD-demo[20008:5024950] 2---<NSThread: 0x60000026e100>{number = 3, name = (null)}
- 2018-02-23 20:41:27.047690+0800 YSC-GCD-demo[20008:5024950] 3---<NSThread: 0x60000026e100>{number = 3, name = (null)}
- 2018-02-23 20:41:29.052327+0800 YSC-GCD-demo[20008:5024950] 3---<NSThread: 0x60000026e100>{number = 3, name = (null)}
在
异步执行 + 串行队列
可以看到:
开启了一条新线程(异步执行具备开启新线程的能力, 串行队列只开启一个线程)
所有任务是在打印的
syncConcurrent---begin
和
syncConcurrent---end
之后才开始执行的(异步执行不会做任何等待, 可以继续执行任务)
任务是按顺序执行的(串行队列每次只有一个任务被执行, 任务一个接一个按顺序执行)
下边讲讲刚才我们提到过的特殊队列: 主队列
主队列: GCD 自带的一种特殊的串行队列
所有放在主队列中的任务, 都会放到主线程中执行
可使用
dispatch_get_main_queue()
获得主队列
我们再来看看主队列的两种组合方式
4.5 同步执行 + 主队列
同步执行 + 主队列
在不同线程中调用结果也是不一样, 在主线程中调用会出现死锁, 而在其他线程中则不会
4.5.1 在主线程中调用
同步执行 + 主队列
互相等待卡住不可行
- /**
- * 同步执行 + 主队列
- * 特点(主线程调用): 互等卡主不执行
- * 特点(其他线程调用): 不会开启新线程, 执行完一个任务, 再执行下一个任务
- */
- - (void)syncMain {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"syncMain---begin");
- dispatch_queue_t queue = dispatch_get_main_queue();
- dispatch_sync(queue, ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_sync(queue, ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_sync(queue, ^{
- // 追加任务 3
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- NSLog(@"syncMain---end");
- }
输出结果:
- 2018-02-23 20:42:36.842892+0800 YSC-GCD-demo[20041:5030982] currentThread---<NSThread: 0x600000078a00>{number = 1, name = main}
- 2018-02-23 20:42:36.843050+0800 YSC-GCD-demo[20041:5030982] syncMain---begin
- (lldb)
在
同步执行 + 主队列
可以惊奇的发现:
在主线程中使用
同步执行 + 主队列
, 追加到主线程的任务 1 任务 2 任务 3 都不再执行了, 而且 syncMain---end 也没有打印, 在 XCode 9 上还会报崩溃这是为什么呢?
这是因为我们在主线程中执行 syncMain 方法, 相当于把 syncMain 任务放到了主线程的队列中而同步执行会等待当前队列中的任务执行完毕, 才会接着执行那么当我们把任务 1 追加到主队列中, 任务 1 就在等待主线程处理完 syncMain 任务而 syncMain 任务需要等待任务 1 执行完毕, 才能接着执行
那么, 现在的情况就是 syncMain 任务和任务 1 都在等对方执行完毕这样大家互相等待, 所以就卡住了, 所以我们的任务执行不了, 而且 syncMain---end 也没有打印
要是如果不在主线程中调用, 而在其他线程中调用会如何呢?
4.5.2 在其他线程中调用
同步执行 + 主队列
不会开启新线程, 执行完一个任务, 再执行下一个任务
// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程, 并自动启动线程执行
selector 任务
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
输出结果:
- 2018-02-23 20:44:19.377321+0800 YSC-GCD-demo[20083:5040347] currentThread---<NSThread: 0x600000272fc0>{number = 3, name = (null)}
- 2018-02-23 20:44:19.377494+0800 YSC-GCD-demo[20083:5040347] syncMain---begin
- 2018-02-23 20:44:21.384716+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}
- 2018-02-23 20:44:23.386091+0800 YSC-GCD-demo[20083:5040132] 1---<NSThread: 0x60000006c900>{number = 1, name = main}
- 2018-02-23 20:44:25.387687+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}
- 2018-02-23 20:44:27.388648+0800 YSC-GCD-demo[20083:5040132] 2---<NSThread: 0x60000006c900>{number = 1, name = main}
- 2018-02-23 20:44:29.390459+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}
- 2018-02-23 20:44:31.391965+0800 YSC-GCD-demo[20083:5040132] 3---<NSThread: 0x60000006c900>{number = 1, name = main}
- 2018-02-23 20:44:31.392513+0800 YSC-GCD-demo[20083:5040347] syncMain---end
在其他线程中使用
同步执行 + 主队列
可看到:
所有任务都是在主线程 (非当前线程) 中执行的, 没有开启新的线程(所有放在主队列中的任务, 都会放到主线程中执行)
所有任务都在打印的
syncConcurrent---begin
和
syncConcurrent---end
之间执行(同步任务需要等待队列的任务执行结束)
任务是按顺序执行的(主队列是串行队列, 每次只有一个任务被执行, 任务一个接一个按顺序执行)
为什么现在就不会卡住了呢? 因为 syncMain 任务放到了其他线程里, 而任务 1 任务 2 任务 3 都在追加到主队列中, 这三个任务都会在主线程中执行 syncMain 任务在其他线程中执行到追加任务 1 到主队列中, 因为主队列现在没有正在执行的任务, 所以, 会直接执行主队列的任务 1, 等任务 1 执行完毕, 再接着执行任务 2 任务 3 所以这里不会卡住线程
4.6 异步执行 + 主队列
只在主线程中执行任务, 执行完一个任务, 再执行下一个任务
- /**
- * 异步执行 + 主队列
- * 特点: 只在主线程中执行任务, 执行完一个任务, 再执行下一个任务
- */
- - (void)asyncMain {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"asyncMain---begin");
- dispatch_queue_t queue = dispatch_get_main_queue();
- dispatch_async(queue, ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_async(queue, ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_async(queue, ^{
- // 追加任务 3
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- NSLog(@"asyncMain---end");
- }
输出结果:
- 2018-02-23 20:45:49.981505+0800 YSC-GCD-demo[20111:5046708] currentThread---<NSThread: 0x60000006d440>{number = 1, name = main}
- 2018-02-23 20:45:49.981935+0800 YSC-GCD-demo[20111:5046708] asyncMain---begin
- 2018-02-23 20:45:49.982352+0800 YSC-GCD-demo[20111:5046708] asyncMain---end
- 2018-02-23 20:45:51.991096+0800 YSC-GCD-demo[20111:5046708] 1---<NSThread: 0x60000006d440>{number = 1, name = main}
- 2018-02-23 20:45:53.991959+0800 YSC-GCD-demo[20111:5046708] 1---<NSThread: 0x60000006d440>{number = 1, name = main}
- 2018-02-23 20:45:55.992937+0800 YSC-GCD-demo[20111:5046708] 2---<NSThread: 0x60000006d440>{number = 1, name = main}
- 2018-02-23 20:45:57.993649+0800 YSC-GCD-demo[20111:5046708] 2---<NSThread: 0x60000006d440>{number = 1, name = main}
- 2018-02-23 20:45:59.994928+0800 YSC-GCD-demo[20111:5046708] 3---<NSThread: 0x60000006d440>{number = 1, name = main}
- 2018-02-23 20:46:01.995589+0800 YSC-GCD-demo[20111:5046708] 3---<NSThread: 0x60000006d440>{number = 1, name = main}
在
异步执行 + 主队列
可以看到:
所有任务都是在当前线程 (主线程) 中执行的, 并没有开启新的线程(虽然异步执行具备开启线程的能力, 但因为是主队列, 所以所有任务都在主线程中)
所有任务是在打印的 syncConcurrent---begin 和 syncConcurrent---end 之后才开始执行的(异步执行不会做任何等待, 可以继续执行任务)
任务是按顺序执行的(因为主队列是串行队列, 每次只有一个任务被执行, 任务一个接一个按顺序执行)
弄懂了难理解绕来绕去的队列 + 任务之后, 我们来学习一个简单的东西: 5. GCD 线程间的通信
5. GCD 线程间的通信
在 iOS 开发过程中, 我们一般在主线程里边进行 UI 刷新, 例如: 点击滚动拖拽等事件我们通常把一些耗时的操作放在其他线程, 比如说图片下载文件上传等耗时操作而当我们有时候在其他线程完成了耗时操作时, 需要回到主线程, 那么就用到了线程之间的通讯
- /**
- * 线程间通信
- */
- - (void)communication {
- // 获取全局并发队列
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- // 获取主队列
- dispatch_queue_t mainQueue = dispatch_get_main_queue();
- dispatch_async(queue, ^{
- // 异步追加任务
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- // 回到主线程
- dispatch_async(mainQueue, ^{
- // 追加在主线程中执行的任务
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- });
- });
- }
输出结果:
- 2018-02-23 20:47:03.462394+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
- 2018-02-23 20:47:05.465912+0800 YSC-GCD-demo[20154:5053282] 1---<NSThread: 0x600000271940>{number = 3, name = (null)}
- 2018-02-23 20:47:07.466657+0800 YSC-GCD-demo[20154:5052953] 2---<NSThread: 0x60000007bf80>{number = 1, name = main}
可以看到在其他线程中先执行任务, 执行完了之后回到主线程执行主线程的相应操作
6. GCD 的其他方法
6.1 GCD 栅栏方法: dispatch_barrier_async
我们有时需要异步执行两组操作, 而且第一组操作执行完之后, 才能开始执行第二组操作这样我们就需要一个相当于 栅栏 一样的一个方法将两组异步执行的操作组给分割起来, 当然这里的操作组里可以包含一个或多个任务这就需要用到
dispatch_barrier_async
方法在两个操作组间形成栅栏
dispatch_barrier_async
函数会等待前边追加到并发队列中的任务全部执行完毕之后, 再将指定的任务追加到该异步队列中然后在
dispatch_barrier_async
函数追加的任务执行完毕之后, 异步队列才恢复为一般动作, 接着追加任务到该异步队列并开始执行具体如下图所示:
- /**
- * 栅栏方法 dispatch_barrier_async
- */
- - (void)barrier {
- dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
- dispatch_async(queue, ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_async(queue, ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_barrier_async(queue, ^{
- // 追加任务 barrier
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
- }
- });
- dispatch_async(queue, ^{
- // 追加任务 3
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_async(queue, ^{
- // 追加任务 4
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"4---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- }
输出结果:
- 2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
- 2018-02-23 20:48:18.297745+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
- 2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059274] 1---<NSThread: 0x600000079d80>{number = 4, name = (null)}
- 2018-02-23 20:48:20.301139+0800 YSC-GCD-demo[20188:5059273] 2---<NSThread: 0x600000079e00>{number = 3, name = (null)}
- 2018-02-23 20:48:22.306290+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
- 2018-02-23 20:48:24.311655+0800 YSC-GCD-demo[20188:5059274] barrier---<NSThread: 0x600000079d80>{number = 4, name = (null)}
- 2018-02-23 20:48:26.316943+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
- 2018-02-23 20:48:26.316956+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}
- 2018-02-23 20:48:28.320660+0800 YSC-GCD-demo[20188:5059273] 4---<NSThread: 0x600000079e00>{number = 3, name = (null)}
- 2018-02-23 20:48:28.320649+0800 YSC-GCD-demo[20188:5059274] 3---<NSThread: 0x600000079d80>{number = 4, name = (null)}
在
dispatch_barrier_async
相关代码执行结果中可以看出:
在执行完栅栏前面的操作之后, 才执行栅栏操作, 最后再执行栅栏后边的操作
6.2 GCD 延时执行方法: dispatch_after
我们经常会遇到这样的需求: 在指定时间 (例如 3 秒) 之后执行某个任务可以用 GCD 的 dispatch_after 函数来实现 需要注意的是: dispatch_after 函数并不是在指定时间之后才开始执行处理, 而是在指定时间之后将任务追加到主队列中严格来说, 这个时间并不是绝对准确的, 但想要大致延迟执行任务, dispatch_after 函数是很有效的
- /**
- * 延时执行方法 dispatch_after
- */
- - (void)after {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"asyncMain---begin");
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- // 2.0 秒后异步追加任务代码到主队列, 并开始执行
- NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程
- });
- }
输出结果:
- 2018-02-23 20:53:08.713784+0800 YSC-GCD-demo[20282:5080295] currentThread---<NSThread: 0x60000006ee00>{number = 1, name = main}
- 2018-02-23 20:53:08.713962+0800 YSC-GCD-demo[20282:5080295] asyncMain---begin
- 2018-02-23 20:53:10.714283+0800 YSC-GCD-demo[20282:5080295] after---<NSThread: 0x60000006ee00>{number = 1, name = main}
在 dispatch_after 相关代码执行结果中可以看出: 在打印 asyncMain---begin 之后大约 2.0 秒的时间, 打印了
after---<NSThread: 0x60000006ee00>{number = 1, name = main}
6.3 GCD 一次性代码(只执行一次):dispatch_once
我们在创建单例或者有整个程序运行过程中只执行一次的代码时, 我们就用到了 GCD 的 dispatch_once 函数使用 dispatch_once 函数能保证某段代码在程序运行过程中只被执行 1 次, 并且即使在多线程的环境下, dispatch_once 也可以保证线程安全
- /**
- * 一次性代码(只执行一次)dispatch_once
- */
- - (void)once {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- // 只执行 1 次的代码(这里面默认是线程安全的)
- });
- }
6.4 GCD 快速迭代方法: dispatch_apply
通常我们会用 for 循环遍历, 但是 GCD 给我们提供了快速迭代的函数 dispatch_applydispatch_apply 按照指定的次数将指定的任务追加到指定的队列中, 并等待全部队列执行结束
我们可以利用异步队列同时遍历比如说遍历 0~5 这 6 个数字, for 循环的做法是每次取出一个元素, 逐个遍历 dispatch_apply 可以同时遍历多个数字
- /**
- * 快速迭代方法 dispatch_apply
- */
- - (void)apply {
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- NSLog(@"apply---begin");
- dispatch_apply(6, queue, ^(size_t index) {
- NSLog(@"%zd---%@",index, [NSThread currentThread]);
- });
- NSLog(@"apply---end");
- }
输出结果:
- 2018-02-23 22:03:18.475499+0800 YSC-GCD-demo[20470:5176805] apply---begin
- 2018-02-23 22:03:18.476672+0800 YSC-GCD-demo[20470:5177035] 1---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
- 2018-02-23 22:03:18.476693+0800 YSC-GCD-demo[20470:5176805] 0---<NSThread: 0x604000070640>{number = 1, name = main}
- 2018-02-23 22:03:18.476704+0800 YSC-GCD-demo[20470:5177037] 2---<NSThread: 0x604000276800>{number = 4, name = (null)}
- 2018-02-23 22:03:18.476735+0800 YSC-GCD-demo[20470:5177036] 3---<NSThread: 0x60000027b800>{number = 5, name = (null)}
- 2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5177035] 4---<NSThread: 0x60000027b8c0>{number = 3, name = (null)}
- 2018-02-23 22:03:18.476867+0800 YSC-GCD-demo[20470:5176805] 5---<NSThread: 0x604000070640>{number = 1, name = main}
- 2018-02-23 22:03:18.477038+0800 YSC-GCD-demo[20470:5176805] apply---end
从 dispatch_apply 相关代码执行结果中可以看出:
0~5 打印顺序不定, 最后打印了 apply---end
因为是在并发队列中异步队执行任务, 所以各个任务的执行时间长短不定, 最后结束顺序也不定但是 apply---end 一定在最后执行这是因为 dispatch_apply 函数会等待全部任务执行完毕
6.5 GCD 的队列组: dispatch_group
有时候我们会有这样的需求: 分别异步执行 2 个耗时任务, 然后当 2 个耗时任务都执行完毕后再回到主线程执行任务这时候我们可以用到 GCD 的队列组
调用队列组的
dispatch_group_async
先把任务放到队列中, 然后将队列放入队列组中或者使用队列组的
dispatch_group_enterdispatch_group_leave
组合 来实现
dispatch_group_async
调用队列组的
dispatch_group_notify
回到指定线程执行任务或者使用
dispatch_group_wait
回到当前线程继续向下执行(会阻塞当前线程)
6.5.1 dispatch_group_notify
监听 group 中任务的完成状态, 当所有的任务都执行完成后, 追加任务到 group 中, 并执行任务
- /**
- * 队列组 dispatch_group_notify
- */
- - (void)groupNotify {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"group---begin");
- dispatch_group_t group = dispatch_group_create();
- dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_group_notify(group, dispatch_get_main_queue(), ^{
- // 等前面的异步任务 1 任务 2 都执行完毕后, 回到主线程执行下边任务
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
- }
- NSLog(@"group---end");
- });
- }
输出结果:
- 2018-02-23 22:05:03.790035+0800 YSC-GCD-demo[20494:5183349] currentThread---<NSThread: 0x604000072040>{number = 1, name = main}
- 2018-02-23 22:05:03.790237+0800 YSC-GCD-demo[20494:5183349] group---begin
- 2018-02-23 22:05:05.792721+0800 YSC-GCD-demo[20494:5183654] 1---<NSThread: 0x60000026f280>{number = 4, name = (null)}
- 2018-02-23 22:05:05.792725+0800 YSC-GCD-demo[20494:5183656] 2---<NSThread: 0x60000026f240>{number = 3, name = (null)}
- 2018-02-23 22:05:07.797408+0800 YSC-GCD-demo[20494:5183656] 2---<NSThread: 0x60000026f240>{number = 3, name = (null)}
- 2018-02-23 22:05:07.797408+0800 YSC-GCD-demo[20494:5183654] 1---<NSThread: 0x60000026f280>{number = 4, name = (null)}
- 2018-02-23 22:05:09.798717+0800 YSC-GCD-demo[20494:5183349] 3---<NSThread: 0x604000072040>{number = 1, name = main}
- 2018-02-23 22:05:11.799827+0800 YSC-GCD-demo[20494:5183349] 3---<NSThread: 0x604000072040>{number = 1, name = main}
- 2018-02-23 22:05:11.799977+0800 YSC-GCD-demo[20494:5183349] group---end
从
dispatch_group_notify
相关代码运行输出结果可以看出: 当所有任务都执行完成之后, 才执行
dispatch_group_notify
block 中的任务
6.5.2 dispatch_group_wait
暂停当前线程(阻塞当前线程), 等待指定的 group 中的任务执行完成后, 才会往下继续执行
- /**
- * 队列组 dispatch_group_wait
- */
- - (void)groupWait {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"group---begin");
- dispatch_group_t group = dispatch_group_create();
- dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- });
- // 等待上面的任务全部完成后, 会往下继续执行(会阻塞当前线程)
- dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
- NSLog(@"group---end");
- }
输出结果:
- 2018-02-23 22:10:16.939258+0800 YSC-GCD-demo[20538:5198871] currentThread---<NSThread: 0x600000066780>{number = 1, name = main}
- 2018-02-23 22:10:16.939455+0800 YSC-GCD-demo[20538:5198871] group---begin
- 2018-02-23 22:10:18.943862+0800 YSC-GCD-demo[20538:5199137] 2---<NSThread: 0x600000464b80>{number = 4, name = (null)}
- 2018-02-23 22:10:18.943861+0800 YSC-GCD-demo[20538:5199138] 1---<NSThread: 0x604000076640>{number = 3, name = (null)}
- 2018-02-23 22:10:20.947787+0800 YSC-GCD-demo[20538:5199137] 2---<NSThread: 0x600000464b80>{number = 4, name = (null)}
- 2018-02-23 22:10:20.947790+0800 YSC-GCD-demo[20538:5199138] 1---<NSThread: 0x604000076640>{number = 3, name = (null)}
- 2018-02-23 22:10:20.948134+0800 YSC-GCD-demo[20538:5198871] group---end
从
dispatch_group_wait
相关代码运行输出结果可以看出: 当所有任务执行完成之后, 才执行
dispatch_group_wait
之后的操作但是, 使用
dispatch_group_wait
会阻塞当前线程
- 6.5.3 dispatch_group_enterdispatch_group_leave
- dispatch_group_enter
标志着一个任务追加到 group, 执行一次, 相当于 group 中未执行完毕任务数 + 1
dispatch_group_leave
标志着一个任务离开了 group, 执行一次, 相当于 group 中未执行完毕任务数 - 1
当 group 中未执行完毕任务数为 0 的时候, 才会使
dispatch_group_wait
解除阻塞, 以及执行追加到
dispatch_group_notify
中的任务
- /**
- * 队列组 dispatch_group_enterdispatch_group_leave
- */
- - (void)groupEnterAndLeave
- {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"group---begin");
- dispatch_group_t group = dispatch_group_create();
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- dispatch_group_enter(group);
- dispatch_async(queue, ^{
- // 追加任务 1
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- }
- dispatch_group_leave(group);
- });
- dispatch_group_enter(group);
- dispatch_async(queue, ^{
- // 追加任务 2
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程
- }
- dispatch_group_leave(group);
- });
- dispatch_group_notify(group, dispatch_get_main_queue(), ^{
- // 等前面的异步操作都执行完毕后, 回到主线程.
- for (int i = 0; i < 2; ++i) {
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程
- }
- NSLog(@"group---end");
- });
- // // 等待上面的任务全部完成后, 会往下继续执行(会阻塞当前线程)
- // dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
- //
- // NSLog(@"group---end");
- }
输出结果:
- 2018-02-23 22:14:17.997667+0800 YSC-GCD-demo[20592:5214830] currentThread---<NSThread: 0x604000066600>{number = 1, name = main}
- 2018-02-23 22:14:17.997839+0800 YSC-GCD-demo[20592:5214830] group---begin
- 2018-02-23 22:14:20.000298+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}
- 2018-02-23 22:14:20.000305+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}
- 2018-02-23 22:14:22.001323+0800 YSC-GCD-demo[20592:5215094] 1---<NSThread: 0x600000277c80>{number = 4, name = (null)}
- 2018-02-23 22:14:22.001339+0800 YSC-GCD-demo[20592:5215095] 2---<NSThread: 0x600000277c40>{number = 3, name = (null)}
- 2018-02-23 22:14:24.002321+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}
- 2018-02-23 22:14:26.002852+0800 YSC-GCD-demo[20592:5214830] 3---<NSThread: 0x604000066600>{number = 1, name = main}
- 2018-02-23 22:14:26.003116+0800 YSC-GCD-demo[20592:5214830] group---end
从
dispatch_group_enterdispatch_group_leave
相关代码运行结果中可以看出: 当所有任务执行完成之后, 才执行 dispatch_group_notify 中的任务这里的
dispatch_group_enterdispatch_group_leave
组合, 其实等同于
dispatch_group_async
6.6 GCD 信号量: dispatch_semaphore
GCD 中的信号量是指 Dispatch Semaphore, 是持有计数的信号类似于过高速路收费站的栏杆可以通过时, 打开栏杆, 不可以通过时, 关闭栏杆在 Dispatch Semaphore 中, 使用计数来完成这个功能, 计数为 0 时等待, 不可通过计数为 1 或大于 1 时, 计数减 1 且不等待, 可通过 Dispatch Semaphore 提供了三个函数
dispatch_semaphore_create
: 创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_signal
: 发送一个信号, 让信号总量加 1
dispatch_semaphore_wait
: 可以使总信号量减 1, 当信号总量为 0 时就会一直等待(阻塞所在线程), 否则就可以正常执行
注意: 信号量的使用前提是: 想清楚你需要处理哪个线程等待(阻塞), 又要哪个线程继续执行, 然后使用信号量
Dispatch Semaphore 在实际开发中主要用于:
保持线程同步, 将异步执行任务转换为同步执行任务
保证线程安全, 为线程加锁
6.6.1 Dispatch Semaphore 线程同步
我们在开发中, 会遇到这样的需求: 异步执行耗时任务, 并使用异步执行的结果进行一些额外的操作换句话说, 相当于, 将将异步执行任务转换为同步执行任务比如说: AFNetworking 中 AFURLSessionManager.m 里面的 tasksForKeyPath: 方法通过引入信号量的方式, 等待异步执行任务结果, 获取到 tasks, 然后再返回该 tasks
- - (NSArray *)tasksForKeyPath:(NSString *)keyPath {
- __block NSArray *tasks = nil;
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
- [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
- if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
- tasks = dataTasks;
- } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
- tasks = uploadTasks;
- } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
- tasks = downloadTasks;
- } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
- tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
- }
- dispatch_semaphore_signal(semaphore);
- }];
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
- return tasks;
- }
下面, 我们来利用 Dispatch Semaphore 实现线程同步, 将异步执行任务转换为同步执行任务
- /**
- * semaphore 线程同步
- */
- - (void)semaphoreSync {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"semaphore---begin");
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
- __block int number = 0;
- dispatch_async(queue, ^{
- // 追加任务 1
- [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
- NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
- number = 100;
- dispatch_semaphore_signal(semaphore);
- });
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
- NSLog(@"semaphore---end,number = %zd",number);
- }
输出结果:
- 2018-02-23 22:22:26.521665+0800 YSC-GCD-demo[20642:5246341] currentThread---<NSThread: 0x60400006bc80>{number = 1, name = main}
- 2018-02-23 22:22:26.521869+0800 YSC-GCD-demo[20642:5246341] semaphore---begin
- 2018-02-23 22:22:28.526841+0800 YSC-GCD-demo[20642:5246638] 1---<NSThread: 0x600000272300>{number = 3, name = (null)}
- 2018-02-23 22:22:28.527030+0800 YSC-GCD-demo[20642:5246341] semaphore---end,number = 100
从 Dispatch Semaphore 实现线程同步的代码可以看到:
semaphore---end 是在执行完 number = 100; 之后才打印的而且输出结果 number 为 100 这是因为异步执行不会做任何等待, 可以继续执行任务异步执行将任务 1 追加到队列之后, 不做等待, 接着执行
dispatch_semaphore_wait
方法此时 semaphore == 0, 当前线程进入等待状态然后, 异步任务 1 开始执行任务 1 执行到
dispatch_semaphore_signal
之后, 总信号量, 此时 semaphore == 1,
dispatch_semaphore_wait
方法使总信号量减 1, 正在被阻塞的线程 (主线程) 恢复继续执行最后打印
semaphore---end,number = 100
这样就实现了线程同步, 将异步执行任务转换为同步执行任务
6.6.2 Dispatch Semaphore 线程安全和线程同步(为线程加锁)
线程安全: 如果你的代码所在的进程中有多个线程在同时运行, 而这些线程可能会同时运行这段代码如果每次运行结果和单线程运行的结果是一样的, 而且其他的变量的值也和预期的是一样的, 就是线程安全的
若每个线程中对全局变量静态变量只有读操作, 而无写操作, 一般来说, 这个全局变量是线程安全的; 若有多个线程同时执行写操作(更改变量), 一般都需要考虑线程同步, 否则的话就可能影响线程安全
线程同步: 可理解为线程 A 和 线程 B 一块配合, A 执行到一定程度时要依靠线程 B 的某个结果, 于是停下来, 示意 B 运行; B 依言执行, 再将结果给 A;A 再继续操作
举个简单例子就是: 两个人在一起聊天两个人不能同时说话, 避免听不清 (操作冲突) 等一个人说完(一个线程结束操作), 另一个再说(另一个线程再开始操作)
下面, 我们模拟火车票售卖的方式, 实现 NSThread 线程安全和解决线程同步问题
场景: 总共有 50 张火车票, 有两个售卖火车票的窗口, 一个是北京火车票售卖窗口, 另一个是上海火车票售卖窗口两个窗口同时售卖火车票, 卖完为止
6.6.2.1 非线程安全(不使用 semaphore)
先来看看不考虑线程安全的代码:
- /**
- * 非线程安全: 不使用 semaphore
- * 初始化火车票数量卖票窗口 (非线程安全) 并开始卖票
- */
- - (void)initTicketStatusNotSave {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"semaphore---begin");
- self.ticketSurplusCount = 50;
- // queue1 代表北京火车票售卖窗口
- dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
- // queue2 代表上海火车票售卖窗口
- dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
- __weak typeof(self) weakSelf = self;
- dispatch_async(queue1, ^{
- [weakSelf saleTicketNotSafe];
- });
- dispatch_async(queue2, ^{
- [weakSelf saleTicketNotSafe];
- });
- }
- /**
- * 售卖火车票(非线程安全)
- */
- - (void)saleTicketNotSafe {
- while (1) {
- if (self.ticketSurplusCount > 0) { // 如果还有票, 继续售卖
- self.ticketSurplusCount--;
- NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
- [NSThread sleepForTimeInterval:0.2];
- } else { // 如果已卖完, 关闭售票窗口
- NSLog(@"所有火车票均已售完");
- break;
- }
- }
- }
输出结果(部分):
- 2018-02-23 22:25:35.789072+0800 YSC-GCD-demo[20712:5258914] currentThread---<NSThread: 0x604000068880>{number = 1, name = main}
- 2018-02-23 22:25:35.789260+0800 YSC-GCD-demo[20712:5258914] semaphore---begin
2018-02-23 22:25:35.789641+0800 YSC-GCD-demo[20712:5259176] 剩余票数: 48 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}
2018-02-23 22:25:35.789646+0800 YSC-GCD-demo[20712:5259175] 剩余票数: 49 窗口:<NSThread: 0x60000027e740>{number = 4, name = (null)}
2018-02-23 22:25:35.994113+0800 YSC-GCD-demo[20712:5259175] 剩余票数: 47 窗口:<NSThread: 0x60000027e740>{number = 4, name = (null)}
2018-02-23 22:25:35.994129+0800 YSC-GCD-demo[20712:5259176] 剩余票数: 46 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}
2018-02-23 22:25:36.198993+0800 YSC-GCD-demo[20712:5259176] 剩余票数: 45 窗口:<NSThread: 0x60000027db80>{number = 3, name = (null)}
......
可以看到在不考虑线程安全, 不使用 semaphore 的情况下, 得到票数是错乱的, 这样显然不符合我们的需求, 所以我们需要考虑线程安全问题
6.6.2.2 线程安全(使用 semaphore 加锁)
考虑线程安全的代码:
- /**
- * 线程安全: 使用 semaphore 加锁
- * 初始化火车票数量卖票窗口 (线程安全) 并开始卖票
- */
- - (void)initTicketStatusSave {
- NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
- NSLog(@"semaphore---begin");
- semaphoreLock = dispatch_semaphore_create(1);
- self.ticketSurplusCount = 50;
- // queue1 代表北京火车票售卖窗口
- dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
- // queue2 代表上海火车票售卖窗口
- dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
- __weak typeof(self) weakSelf = self;
- dispatch_async(queue1, ^{
- [weakSelf saleTicketSafe];
- });
- dispatch_async(queue2, ^{
- [weakSelf saleTicketSafe];
- });
- }
- /**
- * 售卖火车票(线程安全)
- */
- - (void)saleTicketSafe {
- while (1) {
- // 相当于加锁
- dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
- if (self.ticketSurplusCount > 0) { // 如果还有票, 继续售卖
- self.ticketSurplusCount--;
- NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
- [NSThread sleepForTimeInterval:0.2];
- } else { // 如果已卖完, 关闭售票窗口
- NSLog(@"所有火车票均已售完");
- // 相当于解锁
- dispatch_semaphore_signal(semaphoreLock);
- break;
- }
- // 相当于解锁
- dispatch_semaphore_signal(semaphoreLock);
- }
- }
输出结果为:
- 2018-02-23 22:32:19.814232+0800 YSC-GCD-demo[20862:5290531] currentThread---<NSThread: 0x6000000783c0>{number = 1, name = main}
- 2018-02-23 22:32:19.814412+0800 YSC-GCD-demo[20862:5290531] semaphore---begin
2018-02-23 22:32:19.814837+0800 YSC-GCD-demo[20862:5290687] 剩余票数: 49 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
2018-02-23 22:32:20.017745+0800 YSC-GCD-demo[20862:5290689] 剩余票数: 48 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:20.222039+0800 YSC-GCD-demo[20862:5290687] 剩余票数: 47 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
......
2018-02-23 22:32:29.024817+0800 YSC-GCD-demo[20862:5290689] 剩余票数: 4 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:29.230110+0800 YSC-GCD-demo[20862:5290687] 剩余票数: 3 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
2018-02-23 22:32:29.433615+0800 YSC-GCD-demo[20862:5290689] 剩余票数: 2 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:29.637572+0800 YSC-GCD-demo[20862:5290687] 剩余票数: 1 窗口:<NSThread: 0x6040002709c0>{number = 3, name = (null)}
2018-02-23 22:32:29.840234+0800 YSC-GCD-demo[20862:5290689] 剩余票数: 0 窗口:<NSThread: 0x60000046c640>{number = 4, name = (null)}
2018-02-23 22:32:30.044960+0800 YSC-GCD-demo[20862:5290687] 所有火车票均已售完
2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] 所有火车票均已售完
可以看出, 在考虑了线程安全的情况下, 使用 dispatch_semaphore 机制之后, 得到的票数是正确的, 没有出现混乱的情况我们也就解决了多个线程同步的问题
参考资料:
iOS GCD 之 dispatch_semaphore(信号量)
iOS 多线程详尽总结系列文章:
iOS 多线程: pthreadNSThread 详尽总结
来源: https://juejin.im/post/5a90de68f265da4e9b592b40