本文介绍iOS 多线程中 GCD 的相关知识以及使用方法。通过本文可以了解到:
- GCD 简介
- GCD 任务和队列
- GCD 的使用和步骤
- GCD 的基本使用
- GCD 线程间的通信
- GCD 的其他使用方法(栅栏方法:dispatch_barrier_async、延时执行: dispatch_after、一次性执行:dispatch_once、队列组:dispatch_group、信号量:dispatch_semaphore、快速迭代方法:dispatch_apply)
1、GCD 简介
全称:GCD为Grand Central Dispatch的缩写。
Grand Central Dispatch或者GCD,是一套低层API,提供了一种新的方法来进行并发程序编写。从基本功能上讲,GCD有点像NSOperationQueue,他们都允许程序将任务切分为多个单一任务然后提交至工作队列来并发地或者串行地执行。GCD 和 NSOpertionQueue 一样都是基于队列的并发编程PAI。GCD比之NSOpertionQueue 更底层更高效,并且它不是Cocoa框架的一部分。
为什么要用GCD ?
- GCD 可以用于多核的并发运算
- GCD 会自动利用更多的 CPU 内核(双核、四核)
- GCD 自动管理线程的生命周期 (创建线程、调度任务、销毁线程)
- 程序员只需要告诉 GCD 想要如何执行什么任务,不需要编写任何线程管理代码
- 易用: GCD比之thread跟简单易用。由于GCD基于work unit而非像thread那样基于运算,所以GCD可以控制诸如等待任务结束、监视文件描述符、周期执行代码以及工作挂起等任务。基于block的血统导致它能极为简单得在不同代码作用域之间传递上下文。
- 效率: GCD被实现得如此轻量和优雅,使得它在很多地方比之专门创建消耗资源的线程更实用且快速。这关系到易用性:导致GCD易用的原因有一部分在于你可以不用担心太多的效率问题而仅仅使用它就行了。
- 性能: GCD自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计算效率。
2、GCD 任务和队列
任务:就是将要在线程中执行的代码,将这段代码用block封装好,然后将这个任务添加到指定的执行方式(同步执行和异步执行),等待CPU从队列中取出任务放到对应的线程中执行。执行任务有两种方式: 同步任务(sync) 和 异步任务(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。
- 同步任务(sync)
- 同任务添加到制定的队列中,在添加的任务执行结束直线,会一直等待,知道队列里面的任务执行完成之后再继续执行。
- 只在当前线程中执行任务,不具备开启线程的能力。
- 异步任务(async)
- 异步添加任务到指定的队列中,它不会做任何的等待,可以继续执行任务。
- 开启多个新线程,任务同一时间可以一起执行。具备开启新线程的能力。异步是多线程的代名词。
- 注意 异步执行(async) 虽然具备开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。
队列(Dispatch Queue):任务执行的等待队列,用来存放你任务的队列。队列是一种特殊的线性表,采用(FIFO)先进先出的原则。即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。没读取一个任务,则从队列中释放一个任务。
在 GCD 中有两种队列:串行队列 和 并行队列。两者都符合FIFO 原则。
两者的区别在于:执行顺序不同,以及开启线程数不同。
-
串行队列(Serial Dispatch Queue):
- 每次只有一个任务被执行。任务是一个接一个的执行的。
-
并发队列(Concurrent Dispatch Queue):
- 可以让多个任务并发(同时)执行。可以开启多条线程。
注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效
3、 GCD 的使用步骤
CGD 的使用步骤:
1、创建队列
2、将任务添加到队列
3.1 队列的创建 / 获取
- 可以使用
dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
来创建队列。第一个参数表示队列的唯一标识符。 Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名,这纯粹是为了debug。这些名字会在崩溃日志中呗显示出来,也可以被调试器调用,这在调试中很有用。;第二个参数用来识别是串行队列还是并发队列。
串行队列:DISPATCH_QUEUE_SERIAL
并发队列:DISPATCH_QUEUE_CONCURRENT
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_SERIAL);
// 并发队列
dispatch_queue_t queue = dispatch_queue_create("net.test.queue", 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来获取。需要传入两个参数。第一个参数表示队列优先级,一般用 0 或 DISPATCH_QUEUE_PRIORITY_DEFAULT。
还有 DISPATCH_QUEUE_PRIORITY_HIGH 和 DISPATCH_QUEUE_PRIORITY_LOW 以及一个优先级更低的后台队列DISPATCH_QUEUE_PRIORITY_BACKGROUND(用于I/O)。
第二个参数暂时没用,用0即可。
- 可以使用dispatch_get_global_queue来获取。需要传入两个参数。第一个参数表示队列优先级,一般用 0 或 DISPATCH_QUEUE_PRIORITY_DEFAULT。
// 全局并发队列的获取方法
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
3.2 任务的创建
GCD 提供了同步执行任务的创建方法dispatch_sync
和异步执行任务创建方法dispatch_async。
// 同步执行任务创建方法
dispatch_sync(queue, ^{
// 这里放同步执行任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
// 这里放异步执行任务代码
});
虽然使用 GCD 只需两步,但是既然我们有两种队列(串行队列/并发队列),两种任务执行方式(同步执行/异步执行),那么我们就有了不同的组合方式:
1、同步执行 + 并发队列
2、异步执行 + 并发队列
3、同步执行 + 串行队列
4、异步执行 + 串行队列
5、异步任务 + 主队列
6、同步任务 + 主队列 (死锁)
区别 | 并发队列 | 串行队列 | 主队列 |
---|---|---|---|
同步(sync) | 没有开启线程的能力,串行执行任务 | 没有开启线程的能力,串行执行任务 | 主线程调用:死锁卡住不执行;其他线程代用:串行执行。 |
异步(async) | 有开启多条线程的能力,并发执行任务 | 有开启一天线程的能力,在新开启的线程中串行执行任务 | 主线程调用:主线程空闲时,串行执行任务。 |
4、GCD 的基本使用
5、GCD 线程间的通信
6、GCD 的其他使用方法
6.1、GCD 栅栏方法:dispatch_barrier_async
- 有时我们需要意不执行两组任务,当第一组任务执行完成后再执行第二组任务。这样我们就用到栅栏进行分割。当前面的任务都执行完成后再继续执行后面的任务。栅栏分同步
dispatch_barrier_sync
和异步dispatch_barrier_async
两种。dispatch_barrier_sync
同步会阻塞当前线程。dispatch_barrier_async
异步不会阻塞当前线程。
- 同步dispatch_barrier_sync
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLog(@"begin---%@",[NSThread currentThread]);
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_sync(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]);
}
});
NSLog(@"end---%@",[NSThread currentThread]);
}
输出结果:
2018-05-17 10:43:56.587267+0800 UniversaTextIApp[12174:4068728] begin---{number = 1, name = main}
2018-05-17 10:43:58.589687+0800 UniversaTextIApp[12174:4068765] 2---{number = 5, name = (null)}
2018-05-17 10:43:58.589714+0800 UniversaTextIApp[12174:4068767] 1---{number = 4, name = (null)}
2018-05-17 10:44:00.591516+0800 UniversaTextIApp[12174:4068765] 2---{number = 5, name = (null)}
2018-05-17 10:44:00.591507+0800 UniversaTextIApp[12174:4068767] 1---{number = 4, name = (null)}
2018-05-17 10:44:02.592663+0800 UniversaTextIApp[12174:4068728] barrier---{number = 1, name = main}
2018-05-17 10:44:04.594309+0800 UniversaTextIApp[12174:4068728] barrier---{number = 1, name = main}
2018-05-17 10:44:04.594776+0800 UniversaTextIApp[12174:4068728] end---{number = 1, name = main}
2018-05-17 10:44:06.602578+0800 UniversaTextIApp[12174:4068767] 3---{number = 4, name = (null)}
2018-05-17 10:44:06.602943+0800 UniversaTextIApp[12174:4068765] 4---{number = 5, name = (null)}
2018-05-17 10:44:08.603940+0800 UniversaTextIApp[12174:4068767] 3---{number = 4, name = (null)}
2018-05-17 10:44:08.604341+0800 UniversaTextIApp[12174:4068765] 4---{number = 5, name = (null)}
- 异步dispatch_barrier_async
- (void)barrier {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
NSLog(@"begin---%@",[NSThread currentThread]);
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]);
}
});
NSLog(@"end---%@",[NSThread currentThread]);
}
输出结果
2018-05-17 10:54:05.267575+0800 UniversaTextIApp[12178:4069997] begin---{number = 1, name = main}
2018-05-17 10:54:05.269201+0800 UniversaTextIApp[12178:4069997] end---{number = 1, name = main}
2018-05-17 10:54:07.272799+0800 UniversaTextIApp[12178:4070038] 2---{number = 5, name = (null)}
2018-05-17 10:54:07.272803+0800 UniversaTextIApp[12178:4070086] 1---{number = 4, name = (null)}
2018-05-17 10:54:09.276679+0800 UniversaTextIApp[12178:4070038] 2---{number = 5, name = (null)}
2018-05-17 10:54:09.278695+0800 UniversaTextIApp[12178:4070086] 1---{number = 4, name = (null)}
2018-05-17 10:54:11.284398+0800 UniversaTextIApp[12178:4070086] barrier---{number = 4, name = (null)}
2018-05-17 10:54:13.290061+0800 UniversaTextIApp[12178:4070086] barrier---{number = 4, name = (null)}
2018-05-17 10:54:15.295960+0800 UniversaTextIApp[12178:4070038] 4---{number = 5, name = (null)}
2018-05-17 10:54:15.295965+0800 UniversaTextIApp[12178:4070086] 3---{number = 4, name = (null)}
2018-05-17 10:54:17.301278+0800 UniversaTextIApp[12178:4070038] 4---{number = 5, name = (null)}
2018-05-17 10:54:17.301292+0800 UniversaTextIApp[12178:4070086] 3---{number = 4, name = (null)}
在dispatch_barrier_async
执行结果中可以看出:
- 在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。不会阻塞当前线程操作。
在dispatch_barrier_sync
执行结果中可以看出:
- 在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作。会阻塞当前线程操作。
6.2 GCD 延时执行方法:dispatch_after
- (void)after {
NSLog(@"begin---%@",[NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"after---%@",[NSThread currentThread]);
});
NSLog(@"end---%@",[NSThread currentThread]);
}
输出结果:
2018-05-17 11:07:07.788990+0800 UniversaTextIApp[12189:4073827] begin---{number = 1, name = main}
2018-05-17 11:07:07.789560+0800 UniversaTextIApp[12189:4073827] end---{number = 1, name = main}
2018-05-17 11:07:09.984709+0800 UniversaTextIApp[12189:4073827] after---{number = 1, name = main}
6.3 GCD 一次性代码(只执行一次):dispatch_once
- 对于某个任务执行一次,且只执行一次。 dispatch_once函数有两个参数,第一个参数predicate用来保证执行一次,第二个参数是要执行一次的任务block。
- dispatch_once被广泛使用在单例、缓存等代码中,用以保证在初始化时执行一次某任务。
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
})
6.4 GCD 快速迭代方法:dispatch_apply
- 通常我们会用 for 循环遍历,但是 GCD 给我们提供了快速迭代的函数dispatch_apply。dispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束。
如果是在串行队列中使用 dispatch_apply,那么就和 for 循环一样,按顺序同步执行。可这样就体现不出快速迭代的意义了。
我们可以利用并发队列进行异步执行。比如说遍历 0~5 这6个数字,for 循环的做法是每次取出一个元素,逐个遍历。dispatch_apply 可以 在多个线程中同时(异步)遍历多个数字。
还有一点,无论是在串行队列,还是异步队列中,dispatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的 dispatch_group_wait方法。
/**
* 快速迭代方法 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");
}
因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不定。但是apply---end一定在最后执行。这是因为dispatch_apply函数会等待全部任务执行完毕。
6.5 GCD 队列组:dispatch_group
有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 dispatch_group_enter、dispatch_group_leave 组合 来实现
dispatch_group_async。
调用队列组的 dispatch_group_notify 回到指定线程执行任务。或者使用 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。
dispatch_group_notify
- 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。可以监视指定dispatch block结束,然后再加入一个block到队列中。三个参数分别为,第一个是需要监视的block,第二个参数是需要提交执行的队列,第三个是待加入到队列中的block。
-(void)group {
dispatch_queue_t queue = dispatch_queue_create("queuq", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
NSLog(@"begin---%@",[NSThread currentThread]);
dispatch_group_async(group, queue, ^{
// 追加任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
// 追加任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);
}
});
dispatch_group_notify(group, queue, ^{
// 追加任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"3---%@",[NSThread currentThread]);
}
});
NSLog(@"end---%@",[NSThread currentThread]);
}
输出结果:
2018-05-17 14:25:54.652947+0800 UniversaTextIApp[12233:4093518] begin---{number = 1, name = main}
2018-05-17 14:25:54.653675+0800 UniversaTextIApp[12233:4093518] end---{number = 1, name = main}
2018-05-17 14:25:56.659627+0800 UniversaTextIApp[12233:4093568] 1---{number = 4, name = (null)}
2018-05-17 14:25:56.660018+0800 UniversaTextIApp[12233:4093566] 2---{number = 5, name = (null)}
2018-05-17 14:25:58.661788+0800 UniversaTextIApp[12233:4093568] 1---{number = 4, name = (null)}
2018-05-17 14:25:58.662182+0800 UniversaTextIApp[12233:4093566] 2---{number = 5, name = (null)}
2018-05-17 14:26:00.666406+0800 UniversaTextIApp[12233:4093566] 3---{number = 5, name = (null)}
2018-05-17 14:26:02.672172+0800 UniversaTextIApp[12233:4093566] 3---{number = 5, name = (null)}
从dispatch_group_notify相关代码运行输出结果可以看出:
当所有任务都执行完成之后,才执行dispatch_group_notify block 中的任务。
dispatch_group_wait
- 暂停当前线程(阻塞当前线程),等待指定的group 中的任务执行完成后,才会执行继续往下执行。
/- (void)wait {
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
NSLog(@"begin---%@",[NSThread currentThread]);
dispatch_group_async(group, queue, ^{
// 追加任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]);
}
});
dispatch_group_async(group, queue, ^{
// 追加任务
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"2---%@",[NSThread currentThread]);
}
});
// 等待上面的任务全部完成。(会阻塞当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"end---%@",[NSThread currentThread]);
}
输出结果:
2018-05-17 14:35:38.473008+0800 UniversaTextIApp[12237:4096345] begin---{number = 1, name = main}
2018-05-17 14:35:40.479561+0800 UniversaTextIApp[12237:4096388] 1---{number = 4, name = (null)}
2018-05-17 14:35:40.479598+0800 UniversaTextIApp[12237:4096379] 2---{number = 5, name = (null)}
2018-05-17 14:35:42.482470+0800 UniversaTextIApp[12237:4096379] 2---{number = 5, name = (null)}
2018-05-17 14:35:42.485232+0800 UniversaTextIApp[12237:4096388] 1---{number = 4, name = (null)}
2018-05-17 14:35:42.485651+0800 UniversaTextIApp[12237:4096345] end---{number = 1, name = main}
从dispatch_group_wait相关代码运行输出结果可以看出:
当所有任务执行完成之后,才执行 dispatch_group_wait 之后的操作。但是,使用dispatch_group_wait 会阻塞当前线程。
dispatch_block_wait:
可以根据dispatch block来设置等待时间,参数DISPATCH_TIME_FOREVER会一直等待block结束
- (void)dispatchBlockWait {
dispatch_queue_t serialQueue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block = dispatch_block_create(0, ^{
NSLog(@"star-->%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:5.f];
NSLog(@"end");
});
dispatch_block_t block1 = dispatch_block_create(0, ^{
NSLog(@"star1-->%@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:5.f];
NSLog(@"end1");
});
dispatch_async(serialQueue, block);
dispatch_async(serialQueue, block1);
//设置DISPATCH_TIME_FOREVER会一直等到前面任务都完成
dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
dispatch_block_wait(block1, DISPATCH_TIME_FOREVER);
NSLog(@"ok, now can go on");
}
6.6 GCD 信号量:dispatch_semaphore
- (void)semaphore {
//dispatch_semaphore 信号量的使用,串行异步操作
// dispatch_semaphore_create 创建一个semaphore
// dispatch_semaphore_signal 发送一个信号
// dispatch_semaphore_wait 等待信号
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 = 10;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}
- (void)semaphore {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];// 模拟耗时操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程
dispatch_semaphore_signal(semaphore);
});
dispatch_group_notify(group, queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
});
}