什么是 GCD ?
Grand Central Dispatch 简称 GCD,是一套底层API,提供了一种新的方法来进行并发程序编写。使用 GCD 来提升程序性能以及发挥多核系统优势。
尽管 GCD 是纯 c 语言的,但它被封装成面向对象的风格。GCD 对象被称为 dispatch object。Dispatch object 像 Cocoa 对象一样是引用计数的。
GCD 的工作原理:
让程序平行的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。
GCD 提供了很多超越传统多线程编程的优势.
- 简单易用,居于 block 的血统,导致它能极为简单的在不同代码作用域之间传递上下文。
- 效率,它在很多地方比之专门创建消耗资源的线程更实用且快速。
- 性能, GCD 自动根据系统负载来增减线程数量.这就减少了上下文切换以及增加了计算效率。
GCD 中有三种队列类型:
- The main queue: 与主线程功能相同。实际上,提交至 main queue 的任务会在主线程中执行。main queue 可以调用 dispatch_get_main_queue() 来获得。因为main queue 是与主线程相关的,所以这是一个串行队列。
- Global queues: 全局队列是并发队列,并由整个进程共享。进程中存在三个全局队列:高、中(默认)、低三个优先级队列。可以调用dispatch_get_global_queue 函数传入优先级来访问队列。
- 用户队列: 用户队列 ( GCD 并不这样称呼这种队列, 但是没有一个特定的名字来形容这种队列,所以我们称其为用户队列) 是用函数 dispatch_queue_create 创建的队列。
有4个术语比较容易混淆:同步、异步、并发、串行
同步或者异步决定了要不要开启新的线程
- 同步: 在当前线程中执行任务,不具备开启新线程的能力。
- 异步: 在新的线程中执行任务,具备开启新线程的能力。
并发和串行决定了任务的执行方式
- 并发: 多个任务并发(同时)执行。
- 串行: 一个任务执行完毕后,再执行下一个任务。
实践
GCD 最基本且常用的函数。
- dispatch_sync() 同步添加操作。他是等待添加进队列里面的操作完成之后再继续执行。
- dispatch_async () 异步添加进任务队列,它不会做任何等待。
- dispatch_get_main_queue() 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行。
- dispatch_queue_create() 创建一个队列。
- dispatch_get_global_queue() 全局并发队列,并由整个进程共享, 可设定优先级来选择高、中、低, 后台优先级队列。说明:全局并发队列的优先级。
DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
// 创建一个队列
dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
// 第一个参数是一个标签,这纯是为了debug。Apple建议我们使用倒置域名来命名队列,比如“com.dreamingwish.subsystem.task”。这些名字会在崩溃日志中被显示出来,也可以被调试器调用这在调试中会很有用,所有尽量不要重名了。
// 第二个参数 设置你的队列是否串行或并行.一般我是设置NULL,它是串行。
// DISPATCH_QUEUE_SERIAL | DISPATCH_QUEUE_CONCURREN
dispatch_sync(dispatch_queue_create("com.buobin.task", NULL), ^{
NSLog(@"current task %@",[NSThread currentThread]);
});
// 前面说过同步或者异步决定了要不要开启新的线程,dispatch_sync 同步添加操作。
// 那么可想而知,打印的线程应该是在当前线程。
GCD[3235:304819] current task <NSThread: 0x100604b50>{number = 1, name = main}
// 如果改为 dispatch_async 呢?
dispatch_async(dispatch_queue_create("com.buobin.task", NULL), ^{
NSLog(@"current task %@",[NSThread currentThread]);
});
// 是的,它开辟了一个新的线程
GCD[3254:306796] current task <NSThread: 0x1006002e0>{number = 2, name = (null)}
// 创建一个全局并发队列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"current task %@",[NSThread currentThread]);
});
再次提醒,使用 dispatch_sync 里面的 block 操作完成之后才继续执行后续代码。而 dispatch_async 无需等待 block 里面的操作是否完成, 继续执行后续代码。
一个开发场景
在一个界面中,要请求3个接口,第3个请求,需要在第1、2请求完成之后再去请求。
最简单的方法,就是一个一个的去请求。虽然这样能解决问题,但是并不能带给我们一个更好的体验。
而 GCD 可以完美的解决这个问题。
// 创建一个调度任务组 它可以将对象关联
dispatch_group_create()创建一个调度任务组 它可以将对象关联
// group 提交到的任务组,这个任务组的对象会一直持续到任务组执行完毕。
// queue 提交到的队列,任务组里不同任务的队列可以不同。
// block 提交的任务。
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
// group监听的任务组。
// queue 执行完毕的这个闭包所在的队列。
// block执行完毕所响应的任务。
dispatchgroup_notify( group: dispatchgroup_t!, queue: dispatch_queue_t!,_block: dispatch_block_t!)
// 示例
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue1 = dispatch_queue_create("task 1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("task 2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue3 = dispatch_queue_create("task 3", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue1, ^{
NSLog(@"请求第一个接口");
sleep(1);
NSLog(@"第一个接口请求成功");
});
dispatch_group_async(group, queue2, ^{
NSLog(@"请求第二个接口");
sleep(1);
NSLog(@"第二个接口请求成功");
});
dispatch_group_notify(group, queue3, ^{
sleep(1);
NSLog(@"第一与第二请求完毕,请求第三个接口");
dispatch_sync(dispatch_get_main_queue(), ^{
sleep(1);
NSLog(@"第三个接口请求成功,回到主线程更新界面");
});
});
GCD 还提供了一个简化方法叫做 dispatch_apply 这个函数可以调用单一block 多次。
模拟场景:比如我们有一个数组,里面的元素有上千个字符串,我们要判断当前字符串它是不是包含某一个字符串,如果是把它提取出来添加到一个新的数组中。直接 for 循环判断,但效率不高。
这时可以使用 dispatch_apply 解决这个问题, 充分发挥多核的优势~
// 获取一个随机整数,范围在[from,to],包括from,不包括to
int getScopeInsideRandomValue(int from, int to) {
return (int)(from + (arc4random() % (to - from + 1)));
}
示例
NSMutableArray *array = [NSMutableArray array];
NSMutableArray *sameArray = [NSMutableArray array];
// 互斥锁(Mutex):是一种用于多线程编程中,防止两条线程同时对同一公共资源(比如全局变量)进行读写的机制。
NSLock *lock = [[NSLock alloc] init];
// 判断依据的字符串
NSString *same = @"8";
for (int i = 0; i < 1000; i++) {
NSString *value = [NSString stringWithFormat:@"%d",getScopeInsideRandomValue(0, 9999)];
[array addObject:value];
}
// 4个线程
size_t taskCount = 4;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(taskCount, queue, ^(size_t index){
NSLog(@"current task %@",[NSThread currentThread]);
NSRange range = NSMakeRange(index * (array.count / 4), array.count / 4);
NSLog(@"range = %@",NSStringFromRange(range));
NSArray *currentArray = [array subarrayWithRange:range];
for (int i = 0; i < currentArray.count; i++) {
NSString *value = [currentArray objectAtIndex:i];
if ([value containsString:same]) {
[lock lock];
[sameArray addObject:value];
[lock unlock];
}
}
});
// 排序
sameArray = [[sameArray sortedArrayUsingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
return [obj1 intValue] > [obj2 intValue];
}] mutableCopy];
NSLog(@"%@",sameArray);
打印结果
GCD[6098:544023] current task <NSThread: 0x10053d2b0>{number = 4, name = (null)}
GCD[6098:544021] current task <NSThread: 0x10053bb80>{number = 3, name = (null)}
GCD[6098:544022] current task <NSThread: 0x1004007a0>{number = 2, name = (null)}
GCD[6098:544023] range = {250, 250}
GCD[6098:544022] range = {500, 250}
GCD[6098:543914] current task <NSThread: 0x100603f00>{number = 1, name = main}
GCD[6098:544021] range = {750, 250}
GCD[6098:543914] range = {0, 250}
GCD[6098:543914] (
18,
81,
188,
238,
285,
387,
...
...
...
)
从打印的结果还可以看出 dispatch_apply 会使用主线程进行处理业务。
如果不想阻塞到主线程,可以把 apply 放在异步线程中。
dispatch_async(queue, ^{
dispatch_apply(taskCount, queue, ^(size_t index){
NSLog(@"current task %@",[NSThread currentThread]);
// code ...
});
});
虽然说 GCD 轻量且低负载,但是将 block 提交至 queue 还是很消耗资源的—— block 需要被拷贝和入队,同时适当的工作线程需要被通知。过度的创建线程,比如几十个,并不能带来更高的运行效率,反而会浪费资源。
如有错误,还望纠正!
网友评论