GCD
GCD全称是GrandCentralDispatch,可译为“⽜逼的中枢调度器”
- 纯C语⾔言,提供了⾮常多强⼤的函数
- GCD的优势
- GCD是苹果公司为多核的并⾏行运算提出的解决⽅方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务(就是把任务放到队列里)不需要编写任何线程管理代码
两个核心概念
任务: 需要执行的对象
队列:储存任务的框框
程序员需要做的两件事
定制任务
添加任务到队列(队列的取出规则:先进先出,后进后出)-
执⾏任务 :
- 同步执行:一人任务结束,在执行下一个任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 异步执行(多线程的代名词):不需要前一个任务结束,就可以执行下一个任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 同步执行:一人任务结束,在执行下一个任务
同步和异步的区别
同步执行:在当前线程中执⾏
异步执行:在另一条线程中执⾏
任务队列
串⾏队列、并发队列、主队列、全局队列
1、串⾏队列
- GCD中获得串⾏行有2种途径
dispatch_queue_t queue = dispatch_queue_create("name", NULL);
// 创建dispatch_release(queue);
// 非ARC需要释放⼿手动创建的队列
2、使⽤主队列 (跟主线程相关联的队列)
- 主队列是GCD⾃自带的⼀种特殊的串⾏队列
- 放在主队列中的任务,都会放到主线程中执⾏行
3、并发队列
GCD默认已经提供了全局的并发队列,供整个应用使用,不需要⼿手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue( dispatch_queue_priority_t priority, unsigned long flags);
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
全局并发队列的优先级
为了适配IOS7 和 IOS8 最近一段时间,优先级都写 0
● #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // ⾼高
● #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
● #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
● #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后 台
4、主队列
- 所有任务都在主线程执行
- 如果主线程当前有执行的任务,主队列中的任务不会调度
- 等待主线程空闲后,主队列才会调度任务
使⽤用 dispatch_get_main_queue()
获得主队列 dispatch_queue_t queue = dispatch_get_main_queue();
GCD演练
// MARK: - 实例
- (void)gcdDemo1 {
// 创建一个串行队列
dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_SERIAL);
// 创建一个block任务
void (^task)() = ^ {
NSLog(@"%@", [NSThread currentThread]);
};
// 设置执行模式:异步执行 同时:添加任务
dispatch_async(q, task);
}
//===============================================================
// MARK: - 串行队列,同步执行
- (void)gcdDemo2 {
// 创建一个串行队列
dispatch_queue_t q = dispatch_queue_create("itcast", NULL);
// 设置执行模式:同步执行 同时:添加任务
for (int i = 0; i < 10; i++) {
NSLog(@"%d---", i);
// 上面的打印和第一句的添加任务到队列,都是在主线程上执行的
dispatch_sync(q, ^ {
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
// 结果是 1 2 3 4 在线程1 一次出来 ,最后输出 come here
//===============================================================
// MARK: - 串行队列,异步执行
- (void)gcdDemo3 {
dispatch_queue_t q = dispatch_queue_create("itcast", NULL);
for (int i = 0; i < 10; i++) {
NSLog(@"%d---", i);
dispatch_async(q, ^ {
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"come 123here");
[NSThread sleepForTimeInterval:2.0];
NSLog(@"come here");
}
/*
首先是通过循环添加任务到串行队列中
0---
1---
2---
3---
4---
5---
6---
7---
8---
9---
因为是异步执行、然后就多线程执行(下面两个的输出顺序不确定)
主线程输出:come here
子线程输出:
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 0
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 1
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 2
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 3
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 4
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 5
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 6
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 7
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 8
<NSThread: 0x7fc3c8499e50>{number = 2, name = (null)} - 9
*/
//===============================================================
// MARK: - 并发队列,异步执行
- (void)gcdDemo4 {
dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT); // COUCURRENT 是表示并发队列
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^ {
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
// 输出结果是:多线程输出,无顺序输出
//===============================================================
// MARK: - 并发队列,同步执行
- (void)gcdDemo5 {
dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
NSLog(@"%d---", i);
// 这里是上面输入下面输入交叉出现,说明同步执行的任务是添加一个,执行一个,只要有任务,就立即执行
dispatch_sync(q, ^ {
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"come here");
}
// 输出结果是:都在主线程,顺序输出,最后come here
// 都是主线程--是因为同步执行就在当前线程实行
// 顺序输出-- 虽然并发队列能多出,但是队列的特性是 先进先出
//===============================================================
// MARK: - 主队列,异步执行
- (void)gcdDemo6 {
dispatch_queue_t q = dispatch_get_main_queue();
for (int i = 0; i < 10; i++) {
NSLog(@"%d---", i);
dispatch_async(q, ^ {
NSLog(@"%@ - %d", [NSThread currentThread], i);
});
}
NSLog(@"睡会");
[NSThread sleepForTimeInterval:2.0];
NSLog(@"come here");
}
/*
1---
2---
.
.
9---
睡会
come here
主队列是负责主线程上调度任务的,必须等主线程的语句执行完毕以后,在执行异步执行的子线程任务
任务顺序输出
*/
//===============================================================
// MARK: - 主队列,同步任务
- (void)gcdDemo7 {
dispatch_queue_t q = dispatch_get_main_queue();
NSLog(@"1---");
dispatch_sync(q, ^{
NSLog(@"来啊");
});
NSLog(@"come here");
}
// 这里会卡死
// 主队列要求:必须主线程执行完,才能执行调用任务
// 同步执行:不执行调用的任务,就不能往下走
//===============================================================
// MARK: - 主队列,同步任务,不死锁(不懂)
- (void)gcdDemo8 {
dispatch_queue_t queue = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
dispatch_queue_t q = dispatch_get_main_queue();
NSLog(@"1--- %@", [NSThread currentThread]);
dispatch_sync(q, ^{
[NSThread sleepForTimeInterval:6.0];
NSLog(@"来啊 %@", [NSThread currentThread]);
});
NSLog(@"come here %@", [NSThread currentThread]);
});
// [NSThread sleepForTimeInterval:1.0];
NSLog(@"到底来不来");
}
// 到底来不来
// 1--- <NSThread: 0x7fbf895c9220>{number = 2, name = (null)}
// 来啊 <NSThread: 0x7fbf8940fe50>{number = 1, name = main}
// come here <NSThread: 0x7fbf895c9220>{number = 2, name = (null)}
// 整个是在 并发队列,异步执行 的大框架中
// 执行路径是 主队列任务--同步执行任务--主队列任务(确定的路径)
// 在这个框架里面,只要需要线程,系统就给提供,所以主队列和同步任务可以分别在两个不同的线程中执行
//===============================================================
#pragma mark - 同步任务的用处
在网络开发中,通常会把很多任务放在后台异步执行,有的时候,有些任务会彼此有"依赖"关系!
例子:小说的网站,用户登录,扣费,下载小说 A,扣费,下载小说 B...
利用同步任务,能够做到,任务依赖关系,前一个同步任务不执行完,队列就不会调度后面的任务!
// MARK: - 同步任务的用处
- (void)gcdDemo9 {
dispatch_queue_t q = dispatch_queue_create("itcast", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(q, ^{
dispatch_sync(dispatch_get_main_queue(), ^ {
NSLog(@"用户登录 %@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(0, 0), ^ {
NSLog(@"下载 A %@", [NSThread currentThread]);
});
dispatch_async(q, ^ {
NSLog(@"扣费 B %@", [NSThread currentThread]);
});
});
[NSThread sleepForTimeInterval:2.0];
NSLog(@"怎么能这样?");
}
// 如果把同步的“用户登录”的位置放在下载和扣费后面,就可能会出现下载 扣费 再登录。如果在用户登录前睡三秒,则肯定会下载 扣费 用户登录 怎么能这样
// 猜测是,主线程只要有任务,就会执行任务,不调度其他线程的任务,所以如果放在下载和扣费后面,在主队类添加任务之前还睡了已汇入,主队列没有添加任务,
// 睡觉的时候,下载和扣费就已经添加到并发队列并异步执行了,因为此时主队列中没有任务 ,就不会阻碍调度,这样就会先输出下载和扣费,
//===============================================================
#pragma mark - 全局队列
// MARK: 全局队列(本质上就是并发队列)
//
- (void)gcdDemo9 {
/**
参数
第一个参数 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(QOS_CLASS_DEFAULT, 0);
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
NSLog(@"com here");
}
全局队列和并发队列的区别
全局队列 & 并发队列
1> 名称,并发队列有名称,适合于商业级软件跟踪错误报告!
2> release, 在 MRC 开发时,并发队列需要使用
dispatch_release(q);
结论:目前绝大多数的软件都会使用全局队列。比较优秀的第三方框架会使用自定义的并发队列!
日常使用:用全局队列!
全局队列和串行队列的选择
全局队列:
- 并发,能够调多个线程,效率高
- 费电,费流量
串行队列
- 如果任务之间需要依赖(ex登陆,下载,扣费),使用串行队列或者主队列
- 省电,省钱,省流量
所以选择的依据是:
- WIFI的情况下,有电,有流量,可以多开线程 (6条)
- 3G/4G蜂窝网络情况下尽量少开,2-3条线程即可
- 时刻替用户省钱省电
延时执行
#pragma mark - 延时执行
- (void)delay {
NSLog(@"come here");
/**
参数:
从现在开始,经过多少纳秒之后,让 queue 队列,调度 block 任务,异步执行!
1. when
2. queue
3. block(dispatch_block 是 iOS 8.0推出的)
*/
// 从现在开始,经过多少纳秒之后
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.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("itcast", NULL), ^{
NSLog(@"%@", [NSThread currentThread]);
});
}
一次性执行
有的时候,我们在开发中,有些代码,从程序启动之后,就只希望执行一次!
尤其在单例设计模式中使用非常普遍,在iOS 开发中,单例的使用,已经到了“滥用”程度!
一次性执行是在当前线程执行的。不会另外分出线程来执行
就算在多线程中测试一次性执行,仍然只是执行一次就不再执行了。
通过在互斥锁之前添加判断,也能达到一次性执行的目的,但是效率相比一次性执行要低很多很多。所以不推荐使用互斥锁
#pragma mark - 一次性执行
- (void)once {
NSLog(@"来了");
// 苹果提供了一个一次行执行的机制,不仅能够保证只被执行一次,而且是"线程安全"的
// 苹果推荐使用 dispatch_once_t 来做一次性执行!因为效率高!
// 不要使用互斥锁,互斥锁效率低!
static dispatch_once_t onceToken;
NSLog(@"%ld", onceToken);
dispatch_once(&onceToken, ^{
// 只会执行一次的代码
NSLog(@"执行了!%@", [NSThread currentThread]);
});
}
@implementation ViewController
long largeNmuber = 1000 * 1000;
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
NSLog(@"%.04f", [self syncDemo1]);
NSLog(@"%.04f", [self onceDemo1]);
NSLog(@"%.04f", [self syncDemo2]);
NSLog(@"%.04f", [self onceDemo2]);
}
//=================================================================
#pragma mark - 单线程测试
- (CFAbsoluteTime)syncDemo1 {
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
// 单线程测试互斥锁
for (int i = 0; i < largeNmuber; ++i) {
[DemoObj syncDemoObj];
}
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
return end - start;
}
- (CFAbsoluteTime)onceDemo1 {
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
// 单线程测试一次性执行
for (int i = 0; i < largeNmuber; ++i) {
[DemoObj onceDemoObj];
}
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
return end - start;
}
最后的输出结果是,可以对比出效率来
单线程互斥锁 0.6975
单线程一次性 0.1761
多线程互斥锁 6.2929
多线程一次性 0.2352
//=================================================================
#pragma mark - 多线程测试
- (CFAbsoluteTime)syncDemo2 {
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
dispatch_group_enter(group);
// 异步执行 测试互斥锁
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int j = 0; j < largeNmuber / 10; j++) {
[DemoObj syncDemoObj];
}
dispatch_group_leave(group);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
return end - start;
}
- (CFAbsoluteTime)onceDemo2 {
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
dispatch_group_enter(group);
// 异步执行 测试 一次性执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int j = 0; j < largeNmuber / 10; j++) {
[DemoObj onceDemoObj];
}
dispatch_group_leave(group);
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
return end - start;
}
@end
调度组
在实际开发中,有时候需要监听多个异步执行的任务的完成情况。等到所有的任务都完成之后再通知,可以用调度组实现
Ex: 下载三个电影,等所有的电影都下载完以后,在通知用户
1、创建队列
2、创建调度组
3、给调度组添加任务
4、所有任务执行完毕通知
注意:一般下载任务都是放在后台线程进行的,但是要把调度组的通知动作放到主线程,因为下载完以后进行通知会有UI的更新
调度组形式一
- (void)group1 {
// 1. 队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 调度组
dispatch_group_t g = dispatch_group_create();
// 3. 添加任务,让队列调度,指定任务执行函数,最终通知群组
dispatch_group_async(g, q, ^{
NSLog(@"download A %@", [NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"download B %@", [NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
[NSThread sleepForTimeInterval:0.8];
NSLog(@"download C %@", [NSThread currentThread]);
});
// 4. 所有任务执行完毕后,获得通知
// 用一个调度组,可以监听全局队列调度的任务,执行完毕后,在主队列执行最终处理!
// dispatch_group_notify 本身是异步的
dispatch_group_notify(g, dispatch_get_main_queue(), ^{
// 更新UI,通知用户!
NSLog(@"OK %@", [NSThread currentThread]);
});
NSLog(@"come here");
// 如果下载完以后,还需要做其他的动作,就不要调用调度组自带的通知动作,课用下面代码实现
// 等待到永远,死等,阻塞住线程执行,一直到所有的任务执行完毕,才会执行后续的代码!
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
NSLog(@"全都完毕");
}
调度组形式二
钓鱼度的任务添加更加灵活
- (void)group2 {
// 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:1.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");
// });
// 等待到永远,死等,阻塞住线程执行,一直到所有的任务执行完毕,才会执行后续的代码!
dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
NSLog(@"全都完毕");
}