GCD基本词汇:
queue 队列
task 任务
synchronize 同步的
asynchronize 异步的
serial 连续的,串行的
concurrent 同时的,并发的
global 全局的
serial dispatch queue 串行队列
concurrent dispatch queue 并发队列
main dispatch queue 主队列
global dispatch queue 全局队列
Functions (函数)
创建和管理队列的函数:
dispatch_get_mian_queue 获取主队列
dispatch_get_global_queue 获取全局队列
dispatch_queue_create 创建队列
dispatch_queue_current_queue获取当前队列
调度任务的函数:
dispatch_async 异步调度队列中的任务
dispatch_sync 同步调度队列中的任务
dispatch_after 延迟执行
dispatch_apply 多次执行某个任务
dispatch_once 一次性执行
调度组函数:
dispatch_group_async 将block提交到队列,异步执行
dispatch_group_create 创建调度组
dispatch_group_enter 进入调度组
dispatch_group_leave 离开调度组
dispatch_group_notify 调度组通知
dispatch_group_wait 调度组等待
Data Types (数据类型)
dispatch_block_t block
dispatch_functions_t 函数
dispatch_group_t 调度组
dispatch_object_t "对象"
dispatch_once_t 一次性执行
dispatch_queue_t 队列
dispatch_time_t 时间
GCD :Grand Central Dispatch 很厉害的中枢调节器
纯c语言的,提供了非常强大的函数
Grand是宏伟的,极重要的意思
GCD的优势
GCD是苹果公司专门为多核的并行运算提出的解决方案
GCD会自动利用更多的cpu内核(比如双核,四核)
GCD会自动管理线程的生命周期(创建任务,调度任务,销毁线程)
程序员只需要告诉GCD要执行什么任务,不需要编写任何线程管理的代码
GCD的核心概念
将任务添加到队列,并且指定执行任务的函数
3个关键概念:
1.任务:执行什么代码
在GCD中,任务是通过block来封装的,并且任务的block没有参数也没有返回值
2.队列:用来存放任务
包括:串行队列,并发队列,主队列,全局队列
3.执行任务的函数:
1>同步:dispatch_sync queue 队列
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t, block);
2>异步
void dispatch_async(dispatch_queue_t queue ,dispatch_block_t block);
GCD的使用步骤:
第一步:创建/获取队列
第二步:创建任务,确定要做的事情
第三步:将任务添加到队列中
1. GCD会自动将对列中的任务取出,放到对应的线程中执行
2. 任务的取出遵循队列的FIFO(first in , first out)原则:先进先出, 后进后出
GCD基本演练
第一步: 获取全局队列
// 1. 获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
第二步: 创建任务
// 2. 创建任务
dispatch_block_t task = ^ {
NSLog(@"hello %@", [NSThread currentThread]);//current:当前的
};
第三步: 将任务添加到队列,并指定执行任务的函数
// 3. 将任务添加到队列,并且指定执行任务的函数
dispatch_async(queue, task);//第一个参数为队列,第二个参数为任务
===========================================================
(2) 精简代码
gcdDemo2:
- (void)gcdDemo2 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"hello %@", [NSThread currentThread]);
});
}
同步函数:任务会在当前线程中执行,因为同步函数不具备开新线程的能力
异步函数:任务会在子线程中执行,因为异步函数具备新线程的能力
与 NSThread相比:
所有代码写在一起,让代码更加简单,易于阅读和维护
(1)1.NSThread 通过@selector 执行要执行的方法
2.GCD通过block执行要执行的代码,指定要执行的方法
(2)使用GCD不需要管理线程的创建/销毁/复用的过程,程序员不用管理线程的生命周期
如果要开多个线程,NSThread必须实例化多个线程对象
GCD队列的演练
1.串行队列(Serial Dispatch Queue)
串行队列的特点:
(1)以先进先出的方式,按顺序调度队列中的任务去执行,一次只能调度一个任务.
(2)无论队列中所指定的执行任务的函数是同步还是异步,都必须等待前一个任务执行完毕,才可以调度后面的任务.
串行对列创建:
(1)dispatch_queue_t queue = dispatch_queue_create("组名". DISPATCH_QUEUE_SERIAL);
(2)dispatch_queue_t queue = dispatch_queue_create("组名",NULL);
1.1串行队列,同步执行
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("组名",DISPATCH_QUEUE_SERIAL);
//将任务添加到队列,并且制定同步执行
dispatch_sync(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread], );
});
不开启线程,顺序执行
1.2串行队列,异步执行
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("组名",DISPATCH_QUEUE_SERIAL);
//将任务添加到队列,并且制定异步执行
dispatch_async(queue, ^{
NSLog(@"%@ %d",[NSThread currentThread], );
});
只会开一条线程,顺序执行
串行队列,不管同步还是异步执行,都按顺序执行,原因:串行队列特点是先进先出
2.并发队列(ConcurrentDispatch Queue)
并发队列的特点:
(1)以先进先出的方式,并发(同时)调度队列中的任务去执行
(2)如果当前调度的任务是同步执行,会等待当前任务执行完毕后,再调度后续的任务
(3)如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,就不会等待当前任务.直接调度额任务到新线程去执行.
并发队列的创建:
dispatch_queue_t queue = dispatch_queue_creat("组名",DISPATCH_QUEUE_CONCURRENT);
2.1并发队列,同步执行
//1.创建并发队列
dispatch_queue_t queue = dispatch_queue_creat("组名",DISPATCH_QUEUE_CONCURRENT);
//2.将任务添加到队列,并且制定同步执行
dispatch_sync(queue, ^{
NSLog(@"%@ %d", [NSThread currentThread],i);
});
不开线程,顺序执行
2.2并发队列,异步执行
//1.创建并发队列
dispatch_queue_t queue = ("组名",DISAPTCH_QUEUE_CONCURRENT);
//2.将任务添加到队列,并且制定异步执行
dispatch_async(queue, ^{
});
开多条线程,乱序执行
3.主队列
主队列是系统提供的,无需自己创建,可以直接通过dispatch_get_main_queue( )函数来获取
主队列的特点:
(1)添加到主队列的任务只能由主线程来执行
(2)以先进先出的方式,只有当主线程的代码执行完毕后,主队列才会调度任务到主线程执行.
3.1 主队列,异步执行
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self gcdDemo1];
}
pragma mark - 主队列, 异步执行
// 在主线程顺序执行, 不开线程
// 主队列的特点: 只有当 主线程空闲时, 主队列才会调度任务到主线程执行
-
(void)gcdDemo1 {
// 1. 获取主队列
dispatch_queue_t q = dispatch_get_main_queue();// 2. 将任务添加到主队列, 并且指定异步执行
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}// 先执行完这句代码, 才会执行主队列中的任务
NSLog(@"hello %@", [NSThread currentThread]);
}
主队列,就算是异步执行,也不会开线程,顺序执行
3.2 主队列,同步执行(死锁)
pragma mark - 主队列, 同步执行
// 主队列, 同步执行, 会死锁
-
(void)gcdDemo2 {
NSLog(@"begin");
//解决方法dispatch_async(dispatch_get_global_queue(0, 0),^{
// 1. 获取主队列
dispatch_queue_t q = dispatch_get_main_queue();
// 2. 将任务添加到主队列, 并且指定同步执行
// 死锁
for (int i = 0; i < 10; i++) {
dispatch_sync(q, ^{
NSLog(@"%@ %d", [NSThread currentThread], i);
});
}
});
NSLog(@"end");
}
在主线程执行,主队列同步执行任务,会发生死锁
主线程和主队列同步任务相互等待,造成死锁
4.全局队列
全局队列是系统提供的,无需自己创建,可以直接通过dispatch_get_global_queue(long identifier, unsigned long flags);函数来获取
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self gcdDemo];
} - (void)gcdDemo {
// 1. 获取全局队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
// 2. 将任务添加到全局队列, 异步执行
for (int i = 0; i < 10; i++) {
dispatch_async(q, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
1.全局对列的工作特点跟并发队列一致.实际上,全局队列就是系统为了方便程序员,专门提供的一种特殊的并发队列.
2.全局队列和并发队列的区别
1>全局队列没有名称
无论ARC还是MRC都不需要考虑内存释放
2>并发队列,有名称
如果在MRC开发中,需要使用dispatch_release来释放相应的对象
dispatch_barrier 必须使用自定义的并发队列
开发第三方框架,建议使用并发队列
3.dispatch_get_global_queue(long identifier, unsigned long flags);
这个函数有两个参数
第一个参数: identifier
iOS7.0,表示的是优先级:
DISPATCH_QUEUE_PRIORITY_HIGH = 2; 高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT = 0; 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW = -2; 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND = INT16_MIN; 后台优先级
iOS8.0开始,推荐使用服务质量(QOS):
QOS_CLASS_USER_INTERACTIVE = 0x21; 用户交互
QOS_CLASS_USER_INITIATED = 0x19; 用户期望
QOS_CLASS_DEFAULT = 0x15; 默认
QOS_CLASS_UTILITY = 0x11; 实用工具
QOS_CLASS_BACKGROUND = 0x09; 后台
QOS_CLASS_UNSPECIFIED = 0x00; 未指定
通过对比可知: 第一个参数传入0,可以同时适配iOS7及iOS7以后的版本。
服务质量和优先级是一一对应的:
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
第二个参数: flags
为未来保留使用的,始终传入0。
Reserved for future use.
总结:
(1) 开不开线程,由执行任务的函数决定
1> 同步执行不开线程
2> 异步执行开线程
(2) 异步执行任务,开几条线程由队列决定
1> 串行队列,只会开一条线程,因为一条就足够了
2> 并发队列,可以开多条线程,具体开几条由线程池决定
在多线程开发中,真正有用的是 并发队列,异步执行,所以应该把注意力放在并发队列异步执行任务上面,其它的仅作了解即可。
六. 同步任务
同步任务的作用:网络开发中,通常有些任务执行会彼此依赖,这个时候就需要同步任务
开发中,经常会遇到类似情况:某些操作之间是有依赖关系的。只有登录了才可以付费,只有付费了才可以下载应用。这些操作必须按顺序执行。
通过同步执行任务,可以实现多个任务之间的依赖关系。
登录、付费、下载应用都需要联网操作,都属于耗时操作,应该放到子线程去执行。因此,这里使用到了 dispatch_async(dispatch_get_global_queue(0, 0), ^{ // 联网操作; };
七、dispatch_barrier(阻塞)
1、barrier的作用
(1) 主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新。
(2) 适合于大规模的I/O操作。
(3) 当访问数据库或文件的时候,更新数据的时候不能和其它更新或读取的操作在同一时间执行,可以使用 dispatch_barrier_async解决。
八、dispatch_after延迟执行
pragma mark - 延迟执行
/**
从现在开始,经过多少纳秒,由队列调度执行block中的代码
参数:
- dispatch_time_t: when 从现在开始, 经过多少纳秒(即延迟多少时间)
- dispatch_queue_t: queue 队列
- dispatch_block_t: block 任务
*/
九、dispatch_once 一次性执行
有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是”单例”。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self once];
}
pragma mark - 一次性执行
/// 一次性执行的原理: 判断静态全局变量的值, 默认是0, 如果执行完成后, 就设置为-1
/// once 内部会判断 onceToken 的值, 如果是0才执行
-
(void)once {
static dispatch_once_t onceToken;
// 如果 onceToken == 0 就执行 block 中的代码
NSLog(@"%zd", onceToken);for (int i = 0; i < 10; i++) {
dispatch_once(&onceToken, ^{
NSLog(@"是一次吗?");
});
}NSLog(@"%zd", onceToken);
}
十、单例
在日常开发中,经常会有一些工具类,在整个项目中都会用到,这个时候通常会将这个工具类封装成一个单例。因为单例在整个程序中始终只会存在一份,不会出现重复创建。
模拟创建一个网络工具类的单例(NetworkTools),并且继承自NSObject。
这里我们通过两种方式来创建单例: 互斥锁 和 dispatch_once, 并且会比较两种方式的性能。
第一步: 创建网络工具类 NetworkTools,继承自 NSObject。
第二步: 提供供全局访问的接口方法
NetworkTools.h
/// 使用互斥锁创建单例
- (instancetype)sharedNetworkTools;
/// 使用 dispatch_once 创建单例
/// 全局的访问方法: 通常都是以 shared 开头
- (instancetype)sharedNetworkToolsOnce;
十一
dispatch_group_enter 和 dispatch_group_leave 必须成对出现。
网友评论