1.0 GCD 概要
1.1 什么是 GCD
Grand Central Dispatch(GCD)是一种异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。
是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。
1.2 用 GCD 的好处
- GCD可用于多核的并行运算
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
1.3 相关概念
任务
指你将要执行的那段代码。比如一个耗时操作。在 GCD 中,一个任务需要添加到一个队列中去执行。
任务的执行又分为同步(sysc)执行和异步(asysc)执行。异步执行一定会开启线程,同步执行一定不会开启新线程。任务的种类 说明 对应函数 同步任务 不开启线程,只在当前线程中执行 dispatch_sync(queue, ^{})
异步任务 开启新线程 dispatch_async(queue, ^{})
- 队列(Dispatch Queue)
指执行处理的等待队列。应用程序编程人员通过 dispatch_asysnc 函数等 API,在 Block 语法中记述想执行的处理并将其追加到 Dispatch Queue 中。Dispatch Queue 按照追加的顺序(先进先出 FIFO,first-in-first-out)执行处理。
在执行处理时存在两种 DispatchQueue,一种是等待现在执行中处理的 Serial Dispatch Queue,另一种是不等待现在执行中处理的 Concurrent Dispatch Queue。
Dispatch Queue 的种类 | 说明 |
---|---|
Serial Dispatch Queue | 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务) |
Concurrent Dispatch Queue | 可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务) |
2.0 GCD 基本使用步骤
2.1 步骤
第一步 :创建一个队列(串行队列或并行队列)
第二步 :将任务添加到队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)
2.2 队列的创建
// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("id", DISPATCH_QUEUE_SERIAL);
// 并行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("id", DISPATCH_QUEUE_CONCURRENT);
// 主队列的创建方法(串行)
dispatch_queue_t queue = dispatch_get_main_queue();
// 全局队列的创建方法(并行)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 第一个参数为优先级,第二个不讨论,传0即可。
2.3 任务的创建
// 同步执行任务创建方法
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 存放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 存放任务代码
});
2.4 组合方式以及结果
任务 | 串行队列 | 并行队列 | 主队列 |
---|---|---|---|
sysc 同步任务 | 不开启线程,串行执行 | 不开启线程,串行执行 | 死锁 |
asysc 异步任务 | 开启一个线程,串行执行 | 开新线程,并行执行 | 在主线程串行执行 |
3.0 GCD 的 基本使用
3.1 dispatch_once 一次性执行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 待执行代码,该代码在程序的生命周期中只执行一次
});
3.2 dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 待执行代码 ,60秒后开始执行
});
3.3 定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 60 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
// 待执行代码 ,每隔60秒执行一次
});
dispatch_resume(timer);
3.4 dispatch_group
任务组操作,一般在开发中我们不会简单的给每个队列只添加一个任务;对于执行多个任务的队列,串行队列依次按照block添加的顺序执行,这样我们可以在最后的block中做处理;但是对于并发队列因为我们不能确定block的执行顺序,所以我们不能确定blocks执行完毕的处理操作应该放在那里,dispatch_group就是为了解决这样的问题而存在的;dispatch_group将提交到队列的blocks化为一个组,不管执行顺序,等组内的blocks全部执行完毕之后dispatch_group_notify的block会进行回调。
注意:关于notify的执行线程根据自己的需要选择是在主线程,还是子线程执行。
3.5 dispatch_barrier
提供一个栅栏方法,用于实现在当前队列同步执行一个block,主要用于多个异步并发任务的分段处理,可以很好的替代dispatch_group
3.5.1 dispatch_barrier_async
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue1 = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"current task");
dispatch_async(queue1, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"A %ld %@", i,[NSThread currentThread]);
}
});
dispatch_async(queue1, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"B %ld %@", i,[NSThread currentThread]);
}
});
dispatch_barrier_async(queue1, ^{
NSLog(@"栅栏函数");
});
dispatch_async(queue1, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"C %ld %@", i,[NSThread currentThread]);
}
});
dispatch_async(queue1, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"D %ld %@", i,[NSThread currentThread]);
}
});
NSLog(@"next task");
}
打印结果:
2018-01-13 16:26:57.873418+0800 ServiceTest[40341:2138955] current task
2018-01-13 16:26:57.873679+0800 ServiceTest[40341:2138955] next task
2018-01-13 16:26:57.873869+0800 ServiceTest[40341:2139080] B 0 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
2018-01-13 16:26:57.873871+0800 ServiceTest[40341:2139079] A 0 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.874106+0800 ServiceTest[40341:2139079] A 1 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.874125+0800 ServiceTest[40341:2139080] B 1 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
2018-01-13 16:26:57.874518+0800 ServiceTest[40341:2139079] A 2 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.874648+0800 ServiceTest[40341:2139080] B 2 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
2018-01-13 16:26:57.874819+0800 ServiceTest[40341:2139079] A 3 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.876463+0800 ServiceTest[40341:2139080] B 3 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
2018-01-13 16:26:57.876608+0800 ServiceTest[40341:2139079] A 4 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.876955+0800 ServiceTest[40341:2139080] B 4 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
2018-01-13 16:26:57.877464+0800 ServiceTest[40341:2139080] 栅栏函数
2018-01-13 16:26:57.878830+0800 ServiceTest[40341:2139079] D 0 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.878836+0800 ServiceTest[40341:2139080] C 0 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
2018-01-13 16:26:57.927685+0800 ServiceTest[40341:2139080] C 1 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
2018-01-13 16:26:57.927737+0800 ServiceTest[40341:2139079] D 1 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.928255+0800 ServiceTest[40341:2139079] D 2 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.928311+0800 ServiceTest[40341:2139080] C 2 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
2018-01-13 16:26:57.928473+0800 ServiceTest[40341:2139079] D 3 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.928586+0800 ServiceTest[40341:2139080] C 3 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
2018-01-13 16:26:57.928663+0800 ServiceTest[40341:2139079] D 4 <NSThread: 0x60000027ba00>{number = 3, name = (null)}
2018-01-13 16:26:57.929181+0800 ServiceTest[40341:2139080] C 4 <NSThread: 0x6040004675c0>{number = 4, name = (null)}
3.5.2 dispatch_barrier_sync
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue1 = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"current task");
dispatch_async(queue1, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"A %ld %@", i,[NSThread currentThread]);
}
});
dispatch_sync(queue1, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"B %ld %@", i,[NSThread currentThread]);
}
});
dispatch_barrier_async(queue1, ^{
NSLog(@"栅栏函数");
});
dispatch_async(queue1, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"C %ld %@", i,[NSThread currentThread]);
}
});
dispatch_async(queue1, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"D %ld %@", i,[NSThread currentThread]);
}
});
NSLog(@"next task");
}
打印结果
2018-01-13 16:30:24.178465+0800 ServiceTest[40377:2144418] current task
2018-01-13 16:30:24.178807+0800 ServiceTest[40377:2144418] B 0 <NSThread: 0x6040000744c0>{number = 1, name = main}
2018-01-13 16:30:24.178835+0800 ServiceTest[40377:2144475] A 0 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.179020+0800 ServiceTest[40377:2144418] B 1 <NSThread: 0x6040000744c0>{number = 1, name = main}
2018-01-13 16:30:24.179020+0800 ServiceTest[40377:2144475] A 1 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.179176+0800 ServiceTest[40377:2144418] B 2 <NSThread: 0x6040000744c0>{number = 1, name = main}
2018-01-13 16:30:24.179422+0800 ServiceTest[40377:2144475] A 2 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.179426+0800 ServiceTest[40377:2144418] B 3 <NSThread: 0x6040000744c0>{number = 1, name = main}
2018-01-13 16:30:24.179665+0800 ServiceTest[40377:2144475] A 3 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.179696+0800 ServiceTest[40377:2144418] B 4 <NSThread: 0x6040000744c0>{number = 1, name = main}
2018-01-13 16:30:24.179935+0800 ServiceTest[40377:2144475] A 4 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.180110+0800 ServiceTest[40377:2144418] 栅栏函数
2018-01-13 16:30:24.180381+0800 ServiceTest[40377:2144418] next task
2018-01-13 16:30:24.180427+0800 ServiceTest[40377:2144475] C 0 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.180458+0800 ServiceTest[40377:2144474] D 0 <NSThread: 0x604000466d80>{number = 4, name = (null)}
2018-01-13 16:30:24.241623+0800 ServiceTest[40377:2144475] C 1 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.241673+0800 ServiceTest[40377:2144474] D 1 <NSThread: 0x604000466d80>{number = 4, name = (null)}
2018-01-13 16:30:24.242565+0800 ServiceTest[40377:2144475] C 2 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.242611+0800 ServiceTest[40377:2144474] D 2 <NSThread: 0x604000466d80>{number = 4, name = (null)}
2018-01-13 16:30:24.243075+0800 ServiceTest[40377:2144475] C 3 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.243265+0800 ServiceTest[40377:2144474] D 3 <NSThread: 0x604000466d80>{number = 4, name = (null)}
2018-01-13 16:30:24.243416+0800 ServiceTest[40377:2144475] C 4 <NSThread: 0x604000466740>{number = 3, name = (null)}
2018-01-13 16:30:24.243762+0800 ServiceTest[40377:2144474] D 4 <NSThread: 0x604000466d80>{number = 4, name = (null)}
3.6 dispatch_semaphore
我们知道多线程开发最难的就是执行顺序的控制,苹果已经给我们封装好了一些流控制的东西,像dispatch_group,等但是有时候某些场景还是需要我们自己实现对代码执行的控制,毕竟我们不希望自己写的代码自己都不知道执行顺序,dispatch_semaphore就是为了这个目的而存在的,我们可以设置一个cout来控制程序按照我们的意愿来执行。
注意:对于这样的阻塞线程的操作,最好不要放在主线程,除非特殊要求。我觉得这应该是我们用多线程开发的共识了。信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被增加。当一个线程在一个信号量上等待时,线程会被阻塞(如果有必要的话),直至计数器大于零,然后线程会减少这个计数。
在GCD中有三个函数是semaphore的操作,分别是:
dispatch_semaphore_create 创建一个semaphore
dispatch_semaphore_signal 发送一个信号
dispatch_semaphore_wait 等待信号
简单的介绍一下这三个函数,第一个函数有一个整形的参数,我们可以理解为信号的总量,dispatch_semaphore_signal是发送一个信号,自然会让信号总量加1,dispatch_semaphore_wait等待信号,当信号总量少于0的时候就会一直等待,否则就可以正常的执行,并让信号总量-1,根据这样的原理,我们便可以快速的创建一个并发控制来同步任务和有限资源访问控制。
还有这么一个比喻:停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal 就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就一直等下去。
// 一个简单的例子
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"current task");
//创建一个并行队列
dispatch_queue_t queque = dispatch_queue_create("GoyakodCreated", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
//异步执行
dispatch_async(queque, ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
// 耗时操作
NSLog(@"耗时操作");
dispatch_semaphore_signal(semaphore);
});
});
// 如果不添加信号量的限制,会先执行下面的打印代码
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"执行完了耗时操作,做进一步处理");
NSLog(@"next task");
}
打印结果
2018-01-13 17:28:26.992137+0800 ServiceTest[40871:2236729] current task
2018-01-13 17:28:31.992451+0800 ServiceTest[40871:2236859] 耗时操作
2018-01-13 17:28:31.992704+0800 ServiceTest[40871:2236729] 执行完了耗时操作,做进一步处理
2018-01-13 17:28:31.992870+0800 ServiceTest[40871:2236729] next task
3.7 快速迭代方法 dispatch_apply
通常我们会用for循环遍历,但是GCD给我们提供了快速迭代的方法dispatch_apply,使我们可以同时遍历。比如说遍历0~5这6个数字,for循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以同时遍历多个数字。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue1 = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT); // 注意,这里如果是DISPATCH_QUEUE_SERIAL 串行队列,结果不开线程,串行遍历
NSLog(@"current task");
dispatch_apply(6, queue1, ^(size_t i) {
NSLog(@"E %ld %@", i,[NSThread currentThread]);
});
NSLog(@"next task");
打印结果
2018-01-13 16:39:24.177032+0800 ServiceTest[40456:2160156] current task
2018-01-13 16:39:24.177384+0800 ServiceTest[40456:2160156] E 0 <NSThread: 0x60000006f140>{number = 1, name = main}
2018-01-13 16:39:24.177496+0800 ServiceTest[40456:2160241] E 1 <NSThread: 0x604000274740>{number = 3, name = (null)}
2018-01-13 16:39:24.177501+0800 ServiceTest[40456:2160240] E 2 <NSThread: 0x600000464680>{number = 4, name = (null)}
2018-01-13 16:39:24.177563+0800 ServiceTest[40456:2160243] E 3 <NSThread: 0x604000274480>{number = 5, name = (null)}
2018-01-13 16:39:24.177690+0800 ServiceTest[40456:2160156] E 4 <NSThread: 0x60000006f140>{number = 1, name = main}
2018-01-13 16:39:24.177697+0800 ServiceTest[40456:2160241] E 5 <NSThread: 0x604000274740>{number = 3, name = (null)}
2018-01-13 16:39:24.178065+0800 ServiceTest[40456:2160156] next task