前言
在开发过程中,我们会经常和网络打交道,与网络相关的知识无疑是iOS开发中的难点之一,学习多线程,我走了很多弯路,遇到过很多坑,不过对于我们来说,学习就是需要从一个坑爬出来,然后又跳到另一个坑.本篇文章主要介绍多线程相关的基本知识,想要深入了解,可以关注CocoaChina微信公众号,里面有很多大牛的技术博客.感谢@小笨狼Lc提供的相关资料.接下来我们正式开始我们的多线程之旅吧.
在正式接触多线程的核心知识前,我们需要了解一些与网络相关的基本知识.比如说:进程和线程分别代表的是什么意思. 两者有什么联系等.
进程与线程的基本概念
- 什么是进程 : 所谓的进程指的就是系统在运行过程中正在运行的一个应用程序.
- 进程的特点 : iOS系统中进程之间是相互独立的,也就是说,每个进程都是在其受保护的内存空间中运行的.这就是为什么iOS系统用起来比某些系统(没有特指)更加的流畅的原因.下面我们根据一副图来说明iOS中进程的特点.
在我们的Mac电脑上同时打开快播和迅雷两款软件,从图片中可以看出,他们之间是没有联系的,两两之间是相互独立的.我们可以通过Mac电脑上的"活动监视器"来查看所有正在开启的进程
什么是线程 :一个进程(后面我们直接就说成应用程序)想要执行任务,那么它必须要有线程,至少要有一条线程,原因是应用程序中的所有的任务都是在线程上执行的,也就是说,没有线程就不能执行任务.
比如根据下面的图片,可以看出当我们开启QQ音乐和快播两款软件时需要一条对应的线程,并且如果我们想要播放音乐或者是下载电影(0),那么他们也要有一条与之对应的线程.总的来说,只要应用程序想要执行某项任务,那么它就必须要有线程,至少要有一条.注意:播放音乐的线程和下载电影的线程之间是相互独立的.
线程的串行与并行
- 线程串行 : 在同一条线程上执行所有的任务,这种运行方式就叫做串行.
- 串行的特点 : 如果一个进程执行任务的方式是串行执行,那么就表示该进程上的所有任务都是在同一条线程上执行的,并且他们的执行方式是按顺序执行的,也就是说,在同时间,一条线程只能执行一个任务,只有当当前任务执行完毕之后,才会去继续执行下一个任务(执行的任务是有序的).
- 比如下面的图片:只有一条线程(黑色箭头).我们需要下载A, B, C三个文件,这时候的执行顺序是,只有当"A文件下载"完毕之后,才会去执行"下载文件B"操作,最后当"下载文件B"执行完毕之后,才去执行"下载文件C".
- 什么是多线程 : 在同一个进程中,拥有多条线程,并行执行多个任务(关于多线程,下面会有更加详细的介绍,这里只是其抛砖引玉的作用).
- 什么是并行 : 多条线程同时执行多个任务(执行的任务是无序的)比如说下面的图片中,开启了三条线程(黑色箭头)分别执行下载对应的文件,就是说:在同一个进程中开启3条线程,同时执行下载操作,实现ABC三个文件同时下载.
- 注意 : 并行执行的本质并不是真正意义上的同时执行,那只是给用户视觉上的错觉.它的实质是在同一时间刻也是只能执行一个任务.它只是线程之间高速切换,给人一种同时下载资源的错觉.(知道就可以了,不必较真儿)
多线程
- 什么是多线程 : 在一个进程中可以开启多条线程,同时执行多个任务.
- 使用多线程的优缺点
- 1, 多线程的优点
- 1.1, 在一定程度上可以提高程序的执行效率
- 1.2, 能够适当提高资源的利用率(比如说:CPU, 内存等利用率)
- 2, 多线程的缺点
- 2.1, 创建线程是有一定的开销的,如果开启大量的线程的话,肯定会降低程序的性能.
- 2.2, 线程开启的越多,CPU在调度线程上的开销就越大
- 2.3, 程序的设计变得更加复杂,比如线程间的通信(下面会详细介绍),又或者多线程的数据共享
iOS中多线程的实现方案
- pthread : 一套通用的多线程并且基于C语言的API,由程序员来管理线程的生命周期,使用难度很大(本篇文章就不介绍了)
- NSThread : 相对于pthread,它更加面向对象,使用起来相对简单,生命周期由程序员来管理,但是在开发中一般不用,使用场景比如说:我想要知道当前线程是主线程还是子线程([NSThread currentThread])
- GCD : 是对NSThread进一步的封装,使用起来更加建档方便快捷,主要是它使用了block代码块.是一套基于C的API,生命周期由系统自动管理.
- NSOperation : 是对GCD进一步的封装,底层是GCD,相对GCD来说,使用更加面向对象,线程的命周期也是由系统自动管理.
GCD
什么是GCD : GCD是Grand Central Disparch的简称,GCD可以自动管理线程的生命周期(创建线程, 调度线程以及销毁线程).所以我们不需要再去关注线程,只需要高数GCD执行的是什么任务以及以什么方式执行任务即可.
GCD的两个核心概念(任务和队列)
- 任务: 所谓的任务就是我们想要执行的代码,换句话说就是block代码块中的代码又或者是一个指针函数.
- 队列 : 可以将队列比喻成存放任务的容器
GCD的两个核心步骤:
- 制定任务 : 即想要实现什么效果的代码
- 添加任务 : 即将确定的任务添加到队列中,GCD会自动将添加的任务取出来,放到对应线程上去执行.这里涉及到任务进出一个原则:FIFO(先进先出)
dispatch_queue_t queue
对象就有内存。跟普通OC对象类似,我们可以用dispatch_retain()和dispatch_release()对其进行内存管理,当一个任务加入到一个queue中的时候,任务会retain这个queue,直到任务执行完成才会release。
在iOS 6之后,dispatch对象已经支持ARC,所以在ARC工程之下,我们可以不用担心他的内存是否被释放.队列的知识补充:要申明一个dispatch的属性。一般情况下我们只需要用strong即可。
@property (nonatomic, strong) dispatch_queue_t queue;
- 注意:如果你是写一个framework,framework的使用者的SDK有可能还是古董级的iOS6之前。那么你需要根据OS_OBJECT_USE_OBJC做一个判断是使用strong还是assign。
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong) dispatch_queue_t queue;
#else
@property (nonatomic, assign) dispatch_queue_t queue;
#endif
GCD中的同步函数与异步函数
异步函数(async) : 可以在新线程上执行任务具备开启新线程的能力.
// 参数传的是一个block
dispatch_async(dispatch_queue_t queue, ^(void)block);
// 参数传的是一个指针函数
dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
两个异步执行的API实现的功能是一样的,都是讲任务提交到指定的队列中,提交后就立即返回,不用等待任务的执行完毕. 提交之后系统内部会对队列queue进行一次retain操作,等到任务执行完毕之后,队列queue再被release一次.它们之间唯一的区别是最后一个参数不同,前者是接收block为参数,后者是接收函数为参数.
**注意(重点:面试可能会被问到) :多线程方法的block中为什么能使用self,不会造成循环引用吗?
**答: 首先肯定是不会有循环引用的,用不着(__weak typeof(self) weakSelf = self).原因:是当使用ispatch_async的时候block会被copy(深拷贝)一次,虽然当block执行任务完成之后block会被release,但是此时的系统已经拥有了之前拷贝的block了,所以用不着担心循环引用问题,也就是说,block中的self不用将之弱化,直接使用即可(注意:这里只是特例,如果没有copy那么还是需要将block里面的self变为weakSelf)
异步是一个比较抽象的概念,简单的说就是将任务加入到队列中之后,立即返回,不需要等待任务的执行.我们用代码加深一下对概念的理解
异步函数示例
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"异步函数之前 %@",[NSThread currentThread]);
/**
* 参数 1: 队列的名称 参数 2: 队列的类型
* DISPATCH_QUEUE_SERIAL NULL // 表示串行队列
* DISPATCH_QUEUE_CONCURRENT // 表示并发队列
*/
dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"异步函数中%@",[NSThread currentThread]);
});
NSLog(@"异步函数之后 %@",[NSThread currentThread]);
}
打印结果
2016-03-21 21:31:17.349 01 - 多线程[998:85748] 异步函数之前 {number = 1, name = main}
2016-03-21 21:31:17.350 01 - 多线程[998:85748] 异步函数之后 {number = 1, name = main}
2016-03-21 21:31:17.350 01 - 多线程[998:85848] 异步函数中{number = 2, name = (null)}
- 结论 : 当使用异步函数时,它只是将任务放置在队列中而已,并不需要等待任务的执行,当将任务提交到队列中时,就会立即返回,根据打印结果可以看出异步函数中的打印是最后才打印的.也就是说,使用异步函数是不会造成线程死锁等问题.
同步函数 : 只能在当前线程上执行任务,不具备开启子线程的能力.
// 参数是block
dispatch_sync(dispatch_queue_t queue, ^(void)block);
// 参数是函数
dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
两个方法的作用完全是相同的,都是讲任务提交到queue中,任务添加到队列中后,不会立即返回,必须要等待任务执行结束之后再返回,他们的唯一的区别就是最后一个接收的参数不一样,一个是接收block代码块,一个接收的是指针函数.
同步表示任务加入到队列中之后不会立即返回,等待任务完成再返回。语言的描述比较抽象,我们再次用代码加深一下对概念的理解
同步函数示例
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"同步函数之前 %@",[NSThread currentThread]);
/**
* 参数 1: 队列的名称 参数 2: 队列的类型
* DISPATCH_QUEUE_SERIAL NULL // 表示串行队列
* DISPATCH_QUEUE_CONCURRENT // 表示并发队列
*/
dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@"同步函数中%@",[NSThread currentThread]);
});
NSLog(@"同步函数之后 %@",[NSThread currentThread]);
}
打印结果
2016-03-21 22:23:28.534 01 - 多线程[1037:95068] 同步函数之前 {number = 1, name = main}
2016-03-21 22:23:28.535 01 - 多线程[1037:95068] 同步函数中{number = 1, name = main}
2016-03-21 22:23:28.535 01 - 多线程[1037:95068] 同步函数之后 {number = 1, name = main}
总结 : 根据打印结果可以看出,它是按顺序执行的,当同步函数将任务添加到队列中后,不会立即返回,而是等待任务执行完毕之后才返回的,而且全部都是在主线程中执行的任务.
先在main queue中执行第一个NSLog
dispatch_sync会将block提交到queue中,等待block的执行
queue中block前面的任务执行完成之后,block执行
block执行完成之后,dispatch_sync返回
dispatch_sync之后的代码执行
线程死锁问题
- 造成死锁的原因 : 由于dispatch_sync(同步)需要等待block被执行,这就非常容易发生死锁。如果一个串行队列,使用dispatch_sync提交block到自己队列中,就会发生死锁
死锁示例
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"同步函数之前 %@",[NSThread currentThread]);
/**
* 参数 1: 队列的名称 参数 2: 队列的类型
* DISPATCH_QUEUE_SERIAL NULL // 表示串行队列
* DISPATCH_QUEUE_CONCURRENT // 表示并发队列
*/
dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"异步函数中%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"同步函数中%@",[NSThread currentThread]);
});
});
NSLog(@"同步函数之后 %@",[NSThread currentThread]);
}
打印结果
2016-03-21 22:40:33.280 01 - 多线程[1112:100793] 同步函数之前 {number = 1, name = main}
2016-03-21 22:40:33.281 01 - 多线程[1112:100793] 同步函数之后 {number = 1, name = main}
2016-03-21 22:40:33.281 01 - 多线程[1112:100849] 异步函数中{number = 2, name = (null)}
- 总结 :
- 上面的代码已经造成了死锁现象,原因是同步(dispatch_sync)将任务添加到队列中后,仍然需要等待任务执行完毕,前面创建队列queue时,它的类型是串行队列,串行队列的本质是一个接一个的执行,所以block执行任务也需要等待前面的任务执行完毕,也就是等待dispatch_sync执行完毕,两者相互谦让,相互等待,导致两个都没有执行,所以执行永远没有执行完毕,这就是造成死锁的本质原因.
- 根据上面打印的结果可以得出造成死锁的条件:
- 创建的队列是串行队列
- 使用同步(dispatch_sync)将任务添加到自己的队列中
- 注意 : 如果queue是并行队列,或者将任务加入到其他队列中,就不会发生死锁的现象了。
写到这里我们需要总结一下,否则以后我们只会越来越害怕多线程,害怕去接触网络相关的知识,做事情不能模棱两可,既然做了,就要弄明白.
- 上面我们学习了多线程执行任务的方法(即同步和异步)和队列的运行方式(串行和并行)这里会很绕,需要弄清楚他们之间的本质,才能更好的运用它们.
- 下面主要是通过它们之间的区别以及其自身本质来区分他们
- 同步和异步的主要区别: 是否具备开启子线程的能力
- 同步 : 只能在当前线程上执行任务,不具备开启新线程的能力
- 异步 : 可以在新线程上执行任务,具备开启新线程的能力
- 串行和并发的主要区别: 任务的执行方式
- 串行 : 只要当当前任务执行完毕之后,才能执行下一个任务,是有序的执行任务
- 并发 : 允许多个任务(并发)同时执行
下面我们具体举例
- 同步函数 + 主队列(死锁啦)
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
// 将任务添加到队列中
dispatch_sync(mainQueue, ^{
NSLog(@"1我的微博@WilliamAlex大叔%@",[NSThread currentThread]);
});
NSLog(@"-------------End");
}
}
打印结果
2016-03-22 10:39:17.524 01 - 多线程[655:22430] -------------begin
- 注意 :示例中的全局队列globalQueue是用来检测线程是不是死锁了.
异步函数 + 主队列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 将任务添加到队列中
dispatch_async(mainQueue, ^{
NSLog(@"哎呀!妈呀, 多线程的水很深啊%@",[NSThread currentThread]);
});
NSLog(@"-------------End");
}
打印结果
2016-03-22 10:44:01.294 01 - 多线程[668:23853] -------------begin
2016-03-22 10:44:01.294 01 - 多线程[668:23853] -------------End
2016-03-22 10:44:01.302 01 - 多线程[668:23853] 哎呀!妈呀, 多线程的水很深啊{number = 1, name = main}
- 注意 : 打印结果表示 : 当使用异步函数时,当将block中的任务添加到队列中后,就会立即返回,不会等待任务的执行.
同步函数 + 串行队列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 获取串行:DISPATCH_QUEUE_SERIAL 或 NULL即可
dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
// 将任务添加到队列中
dispatch_sync(syncSerial, ^{
NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
});
dispatch_sync(syncSerial, ^{
NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
});
dispatch_sync(syncSerial, ^{
NSLog(@"3WilliiamAlex大叔%@",[NSThread currentThread]);
});
NSLog(@"-------------End");
}
打印结果
2016-03-22 10:52:32.478 01 - 多线程[703:27166] -------------begin
2016-03-22 10:52:32.478 01 - 多线程[703:27166] 1WilliiamAlex大叔{number = 1, name = main}
2016-03-22 10:52:32.479 01 - 多线程[703:27166] 2WilliiamAlex大叔{number = 1, name = main}
2016-03-22 10:52:32.479 01 - 多线程[703:27166] 3WilliiamAlex大叔{number = 1, name = main}
2016-03-22 10:52:32.479 01 - 多线程[703:27166] -------------End
- 注意: 从打印结果中看,同步+串行:当将block中的任务添加到队列中后,并不会立即返回,而是等待任务执行完毕,并且任务是一个一个执行的,是有序的,不会开启新的线程.
异步函数 + 串行队列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 获取串行:DISPATCH_QUEUE_SERIAL 或 NULL即可
dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_SERIAL);
// 将任务添加到队列中
dispatch_async(syncSerial, ^{
NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
});
dispatch_async(syncSerial, ^{
NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
});
dispatch_async(syncSerial, ^{
NSLog(@"3WilliiamAlex大叔%@",[NSThread currentThread]);
});
NSLog(@"-------------End");
}
打印结果
2016-03-22 10:57:45.433 01 - 多线程[716:28932] -------------begin
2016-03-22 10:57:45.434 01 - 多线程[716:28932] -------------End
2016-03-22 10:57:45.434 01 - 多线程[716:28983] 1WilliiamAlex大叔{number = 2, name = (null)}
2016-03-22 10:57:45.434 01 - 多线程[716:28983] 2WilliiamAlex大叔{number = 2, name = (null)}
2016-03-22 10:57:45.434 01 - 多线程[716:28983] 3WilliiamAlex大叔{number = 2, name = (null)}
- 注意 : 异步 + 串行 : 是有序执行的,开启了新的线程,注意,只要是异步函数,当将block中的惹怒添加到队列中后会立即返回,不会等待任务的执行.
同步函数 + 并发队列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 获取并发队列 : DISPATCH_QUEUE_CONCURRENT
// dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_CONCURRENT);
// 可以直接使用全局的并发队列(第一个0:表示系统默认即可. 第二个0:表示预留字段)
dispatch_queue_t globalQuque = dispatch_get_global_queue(0, 0);
// 将任务添加到队列中
dispatch_sync(globalQuque, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
}
});
dispatch_sync(globalQuque, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
}
});
NSLog(@"-------------End");
}
打印结果
2016-03-22 11:13:23.988 01 - 多线程[794:35399] -------------begin
2016-03-22 11:13:23.989 01 - 多线程[794:35399] 1WilliiamAlex大叔{number = 1, name = main}
2016-03-22 11:13:23.989 01 - 多线程[794:35399] 1WilliiamAlex大叔{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多线程[794:35399] 1WilliiamAlex大叔{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多线程[794:35399] 2WilliiamAlex大叔{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多线程[794:35399] 2WilliiamAlex大叔{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多线程[794:35399] 2WilliiamAlex大叔{number = 1, name = main}
2016-03-22 11:13:23.990 01 - 多线程[794:35399] -------------End
- 注意 : 同步 + 并发:都是在主线程上执行的,并且执行方式是只有当前面的所有任务有序低执行完毕之后,才会执行下一个任务,不具备开启新线程的能力.
异步函数 + 并发队列
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"-------------begin");
// 1, 获取并发队列 : DISPATCH_QUEUE_CONCURRENT
// dispatch_queue_t syncSerial = dispatch_queue_create("WilliamAlex大叔", DISPATCH_QUEUE_CONCURRENT);
// 可以直接使用全局的并发队列(第一个0:表示系统默认即可. 第二个0:表示预留字段)
dispatch_queue_t globalQuque = dispatch_get_global_queue(0, 0);
// 将任务添加到队列中
dispatch_async(globalQuque, ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"1WilliiamAlex大叔%@",[NSThread currentThread]);
}
});
dispatch_async(globalQuque, ^{
for (NSInteger i = 0; i < 10; i++) {
NSLog(@"2WilliiamAlex大叔%@",[NSThread currentThread]);
}
});
NSLog(@"-------------End");
}
打印结果(部分打印)
2016-03-22 11:09:43.346 01 - 多线程[774:33864] -------------begin
2016-03-22 11:09:43.347 01 - 多线程[774:33864] -------------End
2016-03-22 11:09:43.347 01 - 多线程[774:33908] 2WilliiamAlex大叔{number = 2, name = (null)}
2016-03-22 11:09:43.347 01 - 多线程[774:33907] 1WilliiamAlex大叔{number = 3, name = (null)}
2016-03-22 11:09:43.348 01 - 多线程[774:33908] 2WilliiamAlex大叔{number = 2, name = (null)}
2016-03-22 11:09:43.348 01 - 多线程[774:33907] 1WilliiamAlex大叔{number = 3, name = (null)}
2016-03-22 11:09:43.348 01 - 多线程[774:33908] 2WilliiamAlex大叔
- 注意 : 异步 + 并发:开启了子线程,执行顺序是乱序的,并且将任务添加到队列中后就立即返回了,不用等待任务的执行.
- 补充
- GCD中还提供了一个函数来执行任务,但是它有点特殊.
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
- 该函数的使用场景 : 有时候我们需要让某个任务单独执行,也就是说在执行的时候,不允许其他的任务执行.这时候我们就可以使用dispatch_barrier.
dispatch_barrier示例
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"同步函数之前 %@",[NSThread currentThread]);
/**
* 参数 1: 队列的名称 参数 2: 队列的类型
* DISPATCH_QUEUE_SERIAL NULL // 表示串行队列
* DISPATCH_QUEUE_CONCURRENT // 表示并发队列
*/
dispatch_queue_t queue = dispatch_queue_create("我的微博是@WilliamAlex大叔", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"同步函数中%@",[NSThread currentThread]);
});
dispatch_barrier_async(queue, ^{
NSLog(@"猜猜我在哪里执行%@",[NSThread currentThread]);
});
NSLog(@"同步函数之后 %@",[NSThread currentThread]);
}
打印结果
2016-03-21 23:44:55.517 01 - 多线程[1196:116016] 同步函数之前 {number = 1, name = main}
2016-03-21 23:44:55.517 01 - 多线程[1196:116016] 同步函数中{number = 1, name = main}
2016-03-21 23:44:55.517 01 - 多线程[1196:116016] 同步函数之后 {number = 1, name = main}
2016-03-21 23:44:55.518 01 - 多线程[1196:116084] 猜猜我在哪里执行{number = 2, name = (null)}
- 从打印结果中获取到的信息 : 当我们使用dispatch_barrier将任务添加到队列中,队列中的任务会在前面所有的任务执行完毕后执行,当dispatch_barrier执行任务过程中,其它任务是不允许执行的,直到barrier任务执行完成
- 还有一个有趣的信息,从打印出来的结果中可以看到dispatch_barrier上执行的任务都是在子线程上执行的.所以,dispatch_barrier是将任务添加到并发队列中的.
知识拓展
- dispatch_barrier最典型的使用场景是读写问题,NSMutableDictionary在多个线程中如果同时写入,或者一个线程写入一个线程读取,会发生无法预料的错误。但是他可以在多个线程中同时读取。如果多个线程同时使用同一个NSMutableDictionary。怎样才能保护NSMutableDictionary不发生意外呢?
- (void)setObject:(id)anObject forKey:(id
)aKey
{
dispatch_barrier_async(self.concurrentQueue, ^{
[self.mutableDictionary setObject:anObject forKey:aKey];
});
}
- (id)objectForKey:(id)aKey
{
__block id object = nil;
dispatch_sync(self.concurrentQueue, ^{
object = [self.mutableDictionary objectForKey:aKey];
}); return object;
}
当NSMutableDictionary写入的时候,我们使用dispatch_barrier_async,让其单独执行写入操作,不允许其他写入操作或者读取操作同时执行。当读取的时候,我们只需要直接使用dispatch_sync,让其正常读取即可。这样就可以保证写入时不被打扰,读取时可以多个线程同时进行
总结 : 使用dispatch_barrier的前提是,队列必须是并发队列,但是这个queue(队列)不能是全局队列.
dispatch_barrier的最根本的原理 : 只有它前面所有的任务都执行完毕之后才会执行dispatch_barrier中队列的任务,并且在执行任务过程中,其它任务是不允许执行的,也就是说,当它在执行过程中,只有它在执行.
创建队列
- 创建队列 : 使用dispatch_queue_create函数创建的队列.
/*
参数 1: 队列的名称
参数 2: 队列的类型
* NULL // 表示串行队列
* DISPATCH_QUEUE_SERIAL // 表示串行队列
* DISPATCH_QUEUE_CONCURRENT // 表示并发队列
*/
dispatch_queue_t queue = dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
- 串行队列
使用dispatch_queue_create函数创建串行队列
// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
// 方式 1 : NULL
dispatch_queue_t queue = dispatch_queue_create(@"com.William.Alex", NULL);
// 方式 2 : DISPATCH_QUEUE_SERIAL
dispatch_queue_t queue = dispatch_queue_create(@"com.William.Alex", DISPATCH_QUEUE_SERIAL);
使用主队列(跟主线程相关联的队列)
主队列是GCD自带的一种特殊的串行队列
放在主队列中的任务,都会放到主线程中执行
使用dispatch_get_main_queue()获得主队列
dispatch_queue_t queue = dispatch_get_main_queue();
- 并发队列
dispatch_queue_t queue = dispatch_queue_create(@"com.William.Alex", DISPATCH_QUEUE_CONCURRENT);
- 全局并发队列
GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建
使用dispatch_get_global_queue函数获得全局的并发队列
dispatch_queue_t dispatch_get_global_queue(
dispatch_queue_priority_t priority, // 队列的优先级
unsigned long flags); // 用0即可
获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 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 // 后台
知识拓展
- 全局并发队列函数中的两个参数:
- identifier: 用以标识队列优先级,推荐用qos_class枚举作为参数,也可以使用dispatch_queue_priority_t
- flags: 预留字段,传入任何非0的值都可能导致返回NULL.
- 获取主队列
// 直接获取主队列
NSLog(@"主队列上的任务都是在主线程上执行的%@",dispatch_get_main_queue());
主队列的使用场景
- 主线程是我们最常用的线程,GCD提供了非常简单的获取主线程队列的方法.我们使用主队列或者是主线程主要是执行一些UI界面的操作(比如:UI界面的属性等事件),记住:只要是一些耗时的操作一般都是放在子线程上执行,不耗时的操作就放在主线程上执行.
dispatch_async(dispatch_get_main_queue(), ^{
// 刷新UI界面操作
});
执行加入到主线程队列的block,App会调用dispatch_main(), NSApplicationMain(),或者在主线程使用CFRunLoop。
有的朋友习惯将参数都设置为0
很多时候我们喜欢将0或者NULL传入作为参数
dispatch_get_global_queue(NULL, NULL)
由于NULL等于0,也就是DISPATCH_QUEUE_PRIORITY_DEFAULT,所以返回的是默认优先级
网络延迟
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work);
- 几种延迟操作
- 定时器(NSTimer)
- GCD(dispatch_after)
- 调用NSObject方法(performeSelector)
- NSTimer定时器
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(run) userInfo:nil repeats:YES];
- performeSelector
// 直接调用NSObject方法,,performeSelector方法也是比较常见具体实现是.
// nonnull : 表示参数不能设置为空
// nullable : 表示参数可以设置为空
[self performSelector:(nonnull SEL) withObject:(nullable id) afterDelay:(NSTimeInterval)]
[self performSelector:(nonnull SEL) withObject:(nullable id) afterDelay:(NSTimeInterval) inModes:(nonnull NSArray *)]
- GCD(dispatch_after)
/*
#define DISPATCH_TIME_NOW (0ull)
#define DISPATCH_TIME_FOREVER (~0ull)
注意 : 不能传DISPATCH_TIME_FOREVER,会一直阻塞线程
*/
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0
});
总结延迟操作
函数说明: 2.0秒之后block中的任务会加到队列中,两个函数的第一个参数都是when,表示时间,当我们传入DISPATCH_TIME_NOW当前函数就相当于是一个dispatch_async(异步函数).值得注意的是,我们不能传入DISPATCH_TIME_FOREVER,因为这样会造成线程的阻塞.
参数说明:dispatch_after接收block作为参数,系统持有block,block中self不需要weak。dispatch_after_f接收work函数作为参数,context作为work函数的第一个参数
注意: 需要注意的是这里的延时并不精确的,因为加入队列不一定会立即执行。延时1s可能会1.5s甚至2s之后才会执行。
dispatch_queue_set_specific 和 dispatch_queue_get_specific
dispatch_queue_set_specific的使用场景
- 当我们需要将某些东西关联在队列上,比如说我们想在队列上存储一些东西,又或者我们想区分两个队列。GCD提供了dispatch_queue_set_specific方法,通过key,将context关联到queue上
void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *context, dispatch_function_t destructor);
参数说明
- queue:需要关联的queue,不允许传入nil
- key:队列的标识
- context:要关联的内容,可以为nil
- destructor:释放context的函数,当新的context被设置时,destructor会被调用
dispatch_queue_get_specific的使用场景
- 有存就有取,将context关联到queue上之后,可以通过dispatch_queue_get_specific或者dispatch_get_specific方法将值取出来。
void *dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
void *dispatch_get_specific(const void *key);
dispatch_queue_get_specific的解释
根据queue和key取出context,queue参数不能传入全局队列
dispatch_get_specific: 根据唯一的key取出当前queue的context。如果当前queue没有key对应的context,则去queue的target queue取,取不着返回nil.
如果对全局队列取,也会返回nil
iOS 6之后dispatch_get_current_queue()被废弃,如果我们需要区分不同的queue,可以使用set_specific方法。根据对应的key是否有值来区分
一个人的力量是有限的,如果文章有有错误,希望大家指出来,相互帮助,相互进步.感谢@小笨狼Lc提供资料,想要了解更多知识可以点进连接,关注CocoaChina的微信公众号.http://www.cocoachina.com/ios/20160225/15422.html
后续会持续更新.....加油!!!!!