有坑位的小伙伴内推我哦
本章开始初探
GCD
,理解队列与同步
,异步
函数的搭配使用
0x00 - GCD
简介
- 全称
Grand Central Dispatch
iOS4
引入的多线程编程技术纯c
语言编写, 提供非常强大的函数,也就是说用难以置信的非常简洁的记述方法,实现极为复杂繁琐的多线程编程,可以说是一项划时代的技术。
0x01 - GCD
特性
GCD
是苹果公司为多核的并行运算
提出的解决方案GCD
会自动利用
更多的cpu内核
(比如双核 四核)GCD
会自动管理
线程生命周期 (创建线程, 调度任务,销毁线程)
总结一句话就是将任务添加到队列,并指定任务执行的函数
0x02 - GCD
使用
//方式一
// 创建block任务
dispatch_block_t block = ^{
NSLog(@"GCD基本使用");
};
// 创建queue
dispatch_queue_t q = dispatch_queue_create("com.grz.q1", NULL);
// 将任务加入到队列
dispatch_async(q, block);
// 方式二
dispatch_async( dispatch_queue_create("com.CJL.Queue", NULL), ^{
NSLog(@"GCD基本使用");
});
- 使用
dispatch_block_t
创建任务 - 使用
dispatch_queue_t
创建队列 - 将任务
block
加到队列q
,指定任务的执行方式dispatch_async
⚠️⚠️⚠️ 注意三连:
typedef void (^dispatch_block_t)(void);
- 任务使用
block
封装 - 任务的
block
没有参数也没有返回值
总结就是 任务 + 队列 + 函数
构成。
0x03 - 函数与队列
函数
在
GCD
执行任务的方式有俩种:
同步执行 dispatch_sync
- 等待当前语句执行完,才会执行下边的语句
- 会阻塞当前线程
- 不具备开启新线程能力
- 当前线程中执行block任务
异步执行 dispatch_async
- 不用等待当前语句执行完毕,才会执行下边的语句
- 会开启线程执行block任务,即
具备开启新线程
的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关) - 异步是线程的代名词
队列
开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue
中。
多线程中说的
队列(Dispatch Queue)
指的是执行任务的等待队列,队列是一种特殊的线性表
,遵循FIFO先进先出
的原则, 也就是新任务总是被插到队尾, 从队首读取任务,没读取一个任务,从队列
中释放一个任务。
2种队列
串行队列(Serial Dispatch Queue)
等待现在执行中处理结束,一次只有一个任务被执行,等待上一个任务执行完毕后再执行下一个,
也就是只有一个线程
, 同一时刻只调度一个任务执行
- 使用
dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);
创建串行队列 - 其中的
DISPATCH_QUEUE_SERIAL
也可以使用NULL
表示,这两种均表示默认的串行队列
#define DISPATCH_QUEUE_SERIAL NULL
// 串行队列的获取方法 以下相同效果
dispatch_queue_t serialQueue1 = dispatch_queue_create("com.test.Queue", NULL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("com.test.Queue", DISPATCH_QUEUE_SERIAL);
并发队列(Concurrent Dispatch Queue)
一次
并发执行多个任务
, 开启多个线程, 同时执行任务,同一时刻可以调度多个任务执行)但并行执行的处理数量取决于当前系统的状态, 即
iOS
基于Dispatch Queue
中的处理数,cpu核数以及CPU负荷
等当前系统的状态来决定Concurrent Dispatch Queue
中并发
- 使用
dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);
创建并发队列 - 注意:并发队列的并发功能只有在
异步函数
下才有效
// 并发队列的获取方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.Queue", DISPATCH_QUEUE_CONCURRENT);
在
GCD
中,针对串行
和并发
,系统提供了主队列(Main Dispatch Queue)
和全局并发队列(Global Dispatch Queue)
主队列 Main Dispatch Queue
- 主队列 ,
Main Dispatch Queue
,正如其名称Main
一样,实在主线程中执行的Dispatch Queue
,因为主线程只有一个,所以Main Dispatch Queue
也是Serial Dispatch Queue
,追加到主队列的处理在主线程的Runloop
执行,由于在主线程中执行, 因此要将一些用户界面更新等一些必须在主线程中执行的处理追加到追队列使用,正好与NSObject
中的performSelectorOnMainThread
实例方法这一执行方法相同。 - 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度,也就是先执行主线程的任务。
- 使用
dispatch_get_main_queue()
获得主队列
//主队列的获取方法
dispatch_queue_t mainQueue = dispatch_get_main_queue();
全局队列Global Dispatch Queue
- 为了方便程序员的使用,苹果提供了默认的并发队列
- 在使用多线程开发时,如果对队列没有特殊需求,
在执行异步任务时,可以直接使用全局队列
- 使用
dispatch_get_global_queue
获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
- 第一个参数表示
队列优先级
,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT=0
,在ios9之后,已经被服务质量(quality-of-service)
取代 - 第二个参数使用0
- 第一个参数表示
//全局并发队列的获取方法
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
//优先级从高到低(对应的服务质量)依次为
- DISPATCH_QUEUE_PRIORITY_HIGH -- QOS_CLASS_USER_INITIATED
- DISPATCH_QUEUE_PRIORITY_DEFAULT -- QOS_CLASS_DEFAULT
- DISPATCH_QUEUE_PRIORITY_LOW -- QOS_CLASS_UTILITY
- DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND
0x04 - 函数和队列的搭配使用
01 - 同步函数 + 串行队列
不会开启线程
串行队列中顺序执行
- 会产生堵塞
02 - 同步函数 + 并发队列
不会开启新线程
,当前线程执行任务- 任务一个接着一个
串行队列中顺序执行
03 - 异步函数 + 串行队列
-
- 开启新线程
- 任务
按顺序执行
04 - 异步函数 + 并发队列
- 开启新线程,在当前线程执行任务
- 任务异步执行,
乱序执行任务
,CPU调度有关
05 - 主队列 + 同步函数会发生死锁
具体分析:
dispatch_sync
函数是在主线程中执行的,也就是说本身也是主线程的一部分,主线程的特点是,主线程的代码执行完毕后才会执行主队列的任务,而dispatch_sync
函数是要等待任务执行完返回,任务不执行,dispatch_sync
不返回。这样,
dispatch_sync
为了返回会等任务的执行完毕,而任务又等主线程的代码执行完去调度它,主线程又在等dispatch_sync
的返回。所以发生死锁
。
解决方法
把死锁的方法放到子线程去执行
06 - 主队列 + 异步方法
任务顺序执行
,不开辟新线程
07 - 全局并发队列 + 同步函数
- 任务顺序执行
- 不开辟新线程
08 - 全局并发队列 + 异步函数
- 开辟新线程
- 乱序执行任务
09 - 全局并发队列 + 主队列 配合使用
在日常开发中,全局队列+并发并列
一般是这样配合使用的
//主队列 + 全局并发队列的日常使用
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//执行耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
//回到主线程进行UI操作
});
});
总结
函数\队列 | 串行队列 | 并发队列 | 主队列 | 全局并发队列 |
---|---|---|---|---|
同步函数 | 顺序执行,不开辟线程 | 顺序执行,不开辟线程 | 死锁 | 顺序执行,不开辟线程 |
异步函数 | 顺序执行,开辟线程 | 乱序执行,开辟线程 | 顺序执行,不开辟线程 | 乱序执行,开辟线程 |
0x05 - GCD面试题
01 - 异步函数+并行队列
dispatch_queue_t queue = dispatch_queue_create("com.test.ggg", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1-%@",[NSThread currentThread]);
// 耗时
dispatch_async(queue, ^{
NSLog(@"2-%@",[NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"3-%@",[NSThread currentThread]);
});
NSLog(@"4-%@",[NSThread currentThread]);
});
//sleep(2); // 主队列有大的耗时 或者卡顿就会执行2
NSLog(@"5-%@",[NSThread currentThread]);
----------打印结果-----------
输出顺序为:1 5 2 4 3
主要是异步函数不会阻塞主线程
02 - 异步函数嵌套同步函数 + 并发队列
- (void)interview04{
//并发队列
dispatch_queue_t queue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{ // 耗时
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
// 同步
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
}
----------打印结果-----------
输出顺序为:(1 2 3 无序)0(7 8 9 无序),可以确定的是 0 一定在3之后,在789之前
03 - 下面代码中,队列的类型有几种?
//串行队列 - Serial Dispatch Queue
dispatch_queue_t serialQueue = dispatch_queue_create("com.CJL.Queue", NULL);
//并发队列 - Concurrent Dispatch Queue
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
//主队列 - Main Dispatch Queue
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并发队列 - Global Dispatch Queue
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
队列总共有两种: 并发队列
和 串行队列
- 串行队列:serialQueue、mainQueue
- 并发队列:concurrentQueue、globalQueue
参考
Objective-C高级编程 iOS与OS X多线程和内存管理