线程系列
# 线程
# 什么是多线程?
多线程是指实现多个线程并发执行的技术,进而提升整体处理性能。
同一时间,CPU 只能处理一条线程,多线程并发执行,其实是 CPU 快速的在多条线程之间调度(切换)如果 CPU 调度线程的时间足够快, 就造成了多线程并发执行的假象
- 主线程的栈区 空间大小为1M,非常非常宝贵
- 子线程的栈区 空间大小为512K内存空间
优势
充分发挥多核处理器的优势,将不同线程任务分配给不同的处理器,真正进入“并行计算”状态
弊端 新线程会消耗内存控件和cpu时间,线程太多会降低系统运行性能。
# 进程与线程之间的区别
- 进程:正在运行的程序,负责程序的内存分配,每一个进程都有自己独立的虚拟内存空间。(一个程序运行的动态过程)
- 线程:线程是进程中一个独立执行的路径(控制单元)一个进程至少包含一条线程,即主线程可以将耗时的执行路径(如网络请求)放在其他线程中执行。
# 进程和线程的比较
- 线程是 CPU 调用的最小单位
- 进程是 CPU 分配资源和调度的单位
- 一个程序可以对应多个进程,一个进程中可有多个线程,但至少要有一条线程, 同一个进程内的线程共享进程资源
# 多线程的 并行 和 并发 有什么区别?
并行:充分利用计算机的多核,在多个线程上同步进行
并发: 所谓并发,就是通过一种算法将 CPU 资源合理地分配给多个任务,当一个任务执行 I/O 操作时,CPU 可以转而执行其它的任务,等到 I/O 操作完成以后,或者新的任务遇到 I/O 操作时,CPU 再回到原来的任务继续执行。
# GCD系列
# GCD执行原理?
GCD有一个底层线程池,这个池中存放的是一个个的线程。之所以称为“池”,很容易理解出这个“池”中的线程是可以重用的,当一段时间后这个线程没有被调用胡话,这个线程就会被销毁。注意:开多少条线程是由底层线程池决定的(线程建议控制再3~5条),池是系统自动来维护,不需要我们程序员来维护(看到这句话是不是很开心?) 而我们程序员需要关心的是什么呢?我们只关心的是向队列中添加任务,队列调度即可。
如果队列中存放的是同步任务,则任务出队后,底层线程池中会提供一条线程供这个任务执行,任务执行完毕后这条线程再回到线程池。这样队列中的任务反复调度,因为是同步的,所以当我们用currentThread打印的时候,就是同一条线程。
如果队列中存放的是异步的任务,(注意异步可以开线程),当任务出队后,底层线程池会提供一个线程供任务执行,因为是异步执行,队列中的任务不需等待当前任务执行完毕就可以调度下一个任务,这时底层线程池中会再次提供一个线程供第二个任务执行,执行完毕后再回到底层线程池中。
这样就对线程完成一个复用,而不需要每一个任务执行都开启新的线程,也就从而节约的系统的开销,提高了效率。在iOS7.0的时候,使用GCD系统通常只能开5--8条线程,iOS8.0以后,系统可以开启很多条线程,但是实在开发应用中,建议开启线程条数:3--5条最为合理。
# GCD和NSOperation的比较
GCD 的核心是 C 语言写的系统服务,执行和操作简单高效,因此 NSOperation 底层也通过 GCD 实现,换个说法就是 NSOperation 是对 GCD 更高层次的抽象,这是他们之间最本质的区别。因此如果希望自定义任务,建议使用 NSOperation;
依赖关系,NSOperation 可以设置两个 NSOperation 之间的依赖,第二个任务依赖于第一个任务完成执行,GCD 无法设置依赖关系,不过可以通过dispatch_barrier_async来实现这种效果;
KVO(键值对观察),NSOperation 和容易判断 Operation 当前的状态(是否执行,是否取消),对此 GCD 无法通过 KVO 进行判断;
优先级,NSOperation 可以设置自身的优先级,但是优先级高的不一定先执行,GCD 只能设置队列的优先级,无法在执行的 block 设置优先级;
继承,NSOperation 是一个抽象类,实际开发中常用的两个类是 NSInvocationOperation 和 NSBlockOperation ,同样我们可以自定义 NSOperation,GCD 执行任务可以自由组装,没有继承那么高的代码复用度;
效率,直接使用 GCD 效率确实会更高效,NSOperation 会多一点开销,但是通过 NSOperation 可以获得依赖,优先级,继承,键值对观察这些优势,相对于多的那么一点开销确实很划算,鱼和熊掌不可得兼,取舍在于开发者自己;
# GCD中的队列
GCD中的队列主要有以下四种
主队列:由系统创建的串行队列
获取方式: dispatch_get_main_queue()
全局队列:由系统创建的并发队列
/全局并发队列的获取方法 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
串行队列:自定义的串行队列
获取方式:dispatch_queue_create(@"队列名",DISPATCH_QUEUE_SERIAL)
并行队列:自定义的并行队列
获取方式:dispatch_queue_create(@"队列名",DISPATCH_QUEUE_CONCURRENT)
# 主队列添加同步任务
//主队列同步
// 不会开线程
NSLog(@"start");
// 等
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"a");
});
NSLog(@"b");
2
3
4
5
6
7
运行结果:
程序运行奔溃了,这是为啥子呢?主队列是串行的,执行 b必须要等 a执行完成,而a又必须等b执行完成,这样就互相等待了,就是我们常说的死锁。
当前的代码执行流程,默认就是主队列,也是一个串行队列,任务执行的顺序是:
NSlog(@"start") --> dispathc_sync任务块 --> NSlog(@"b") 在执行任务块时,会向主队列添加一个任务NSlog(@"a"),NSlog(@"a")需要等到NSlog(@"b")执行完成后才能执行,而NSlog(@"b")又要等dispathc_sync任务块执行完成才会执行。这样主队列就会进入一个互相等待状态,这样就是死锁
解决办法:将main_queue主队列改成串行队列或者并发都可以
# 主队列添加异步任务
主队列添加异步任务不会阻塞,不会奔溃
# 并发队列添加异步任务
并发队列添加异步函数执行任务

每个任务复杂度基本一致,异步不会堵塞主线程,打印顺序是: a-e-b-d-c,dispathc_async会开启新的线程去执行其中的任务
# 并发队列添加同步任务
虽然队列是并发的,但是函数是同步,所以任务就是顺序执行,所以打印顺序为:a-b-c-d-e,同步还是不开启线程
# 串行队列添加同步任务
串行队列同步函数执行任务,顺序执行,不会开启新的线程执行任务
# 串行队列添加异步任务
串行队列添加异步函数执行,会开启新的线程执行任务
# 串行队列添加同步和异步混合
该案例和主队列添加同步任务是一样的,主队列也是串行队列。此案例也会崩溃!
dispatch_sync任务块没有执行完,bb 执行不了,dd 又等待 bb的执行,bb任务的执行和 dd 任务的执行互相等待了,死锁。