前言
在开发中,由于一些业务需求,我们不得不使用多线程来实现。那么多线程的原理是怎样的呢,和进程又是怎样的关系?本文将对多线程进行归纳总结下。
线程和进程
- 线程 - 线程是 - 进程的基本执行单元,一个进程的所有任务都在线程中执行
- 进程想要执行任务,必须至少有一条线程 
- 程序启动会默认开启一条线程,也就是我们常说的 - 主线程也称为- UI线程
 
- 进程 - 进程是指在系统中 - 正在运⾏的⼀个应⽤程序,就是一段程序的执行过程,可以理解为手机上的一个- app
- 每个进程之间是独立的,每个进程均运行在其 - 专用且受保护的内存空间内
- 通过 - 活动监视器可以查看- MAC系统中所有开启的进程
 
多线程的意义
- 优点: - 能适当 - 提高程序的执行效率
- 能适当 - 提高资源的利用率(CPU,内容)
- 线程上的 - 任务执行完成后,线程会- 自动销毁
 
- 缺点: - 开启线程需要 - 占用一定的内存空间(- 默认情况下,每一个线程都占- 512KB)
- 如果开启 - 大量的线程,会占用- 大量的内存空间,- 降低程序的性能
- 线程越多,CPU在调用线程上的- 开销就越大
- 多线程使 - 程序设计更加复杂,比如线程间的通信、多线程的数据共享
 
多线程的原理
多线程是CPU在单位时间的时间片内来回调度和切换,构成了很多任务同时进行的假象
- 单线程: - (单核 - CPU)同一时间,- CPU只能处理- 一个线程,换言之就是同一时间只有一个线程在执行
 
- 多线程: - CPU快速的在多个线程之间的切换,- CPU调度线程的时间足够快,就造成了多线程的- 同时执行效果
 
- 如果线程数非常多: - CPU会在- N个线程之间切换,消耗大量的- CPU资源,每个线程被调度的次数就降低
 
线程的生命周期
多线程时CPU在多个线程之间来回切换,也就是有状态之分,当一个线程在被调度时,其他线程是的状态怎样的呢?于是就引出了线程的生命周期
- 线程的生命周期分为5步:- 新建->- 就绪->- 运行->- 阻塞->- 死亡,如下图:

- 新建: - 使用 - new实例化一个线程对象,但该线程对象- 还未使用- start()方法启动线程这个阶段,该阶段只在内存的- 堆中为该对象的实例变量- 分配了内存空间,但线程还- 无法参与- 抢夺CPU的使用权。
 
- 就绪: - 是指一个线程对象使用 - start()方法将线程- 加入可调度线程池后就进入就绪阶段,- 等待CPU来调度后才会执行
 
- 运行: - 当 - CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。线程要想进入运行状态执行,首先必须先处于处于就绪状态中。运行状态可能和就绪状态来回切换,这个和- CPU调度有关。
 
- 阻塞: - 处于运行状态中的线程由于某种原因例如(调用sleep、等待同步锁、从可调度线程池移出等),此时进入阻塞状态。 
- 例如: - sleepForTimeInterval,- sleepUntilDate函数,同步锁- synchronized等可使线程进入阻塞状态
 
- 死亡: - 正常死亡:线程执行完毕。 
- 非正常死亡:当线程因异常而退出,或者调用 - exit
 
可调度线程池

- 工作原理是: 
- 如果都在执行状态,则交给 - 饱和策略处理。
- 如果饱满,则会判断线程是否都处于执行状态,如果没有,则安排非核心线程去执行 
- 如果都在执行,就会检查工作队列是否饱满,如果未饱满,就会将任务存储在工作队列 
- 有新任务来时,会先判断线程池是否都在执行任务,如果 - 没到就会创建线程执行任务
饱和策略
- 饱和策略主要有4种: 
- DisCardPolicy:直接丢弃任务
- DisOldestPolicy:丢掉等待最久的任务
- CallerRunsPolicy:将任务回退到调用者
- AbortPolicy:直接抛出- RejectedExecutionExeception异常来阻止系统正常运行
这四种拒绝策略均由RejectedExecutionHandler接口实现
任务执行的影响因素
任务执行的影响因素通常有4种:
- CPU
- 任务的复杂度 
- 优先级 
- 线程状态 
举例
就例如排两队买奶茶:
- 如果只有一个服务员,肯定速度比较慢,如果有两个,每个人给一队处理,那就明显快很多,这就相当于 - cpu
- 如果一队第一个人之买一杯,另一队正在买的人要十杯,那肯定一杯的先买完,也就是 - 任务复杂度
- 如果两队的第一人都在买,且都买一杯,但其中一个是 - VIP,那就- VIP优先处理,也就是- 优先级
- 如果其中一个服务员,不小心将即将处理好的奶茶打翻了,或者临时接了一个电话,那么也影响了速度,这个就是 - 线程状态因素
优先级
IO密集型的线程特点是频繁等待,而CPU密集型则很少等待,所以CPU密集型的优先级要高,但优先级提高后也不一定执行,只是比低优先级的线程更可能运行,详情请查看 Threading Programming Guide

- 可以通过 - NSThread中的- setThreadPriority:,或者- POSIX的- pthread_setschedparam方法来设置优先级
线程安全
在使用多线程时,往往会出现资源抢夺问题,这时就需要加锁来解决线程安全的问题,也就是将各个线程对同一个数据的访问同步(Synchronization)。所谓同步,即指在一个线程访问数据未结束的时候,其他线程不得对同一个数据进行访问,如此,对数据的访问被原子化了。下面主要介绍:自旋锁、互斥锁
自旋锁
- 自旋锁是计算机科学用于多线程- 同步的一种锁,线程- 反复检查锁变量是否可用。由于线程在这一过程中保持执行,因此是一种- 忙等(忙碌等待)。一旦获取了自旋锁,线程会- 一直持有该锁,- 直至显式释放自旋锁。
互斥锁
- 保证锁内的代码在 - 同一时间只有一条线程能够执行
- 互斥锁的 - 锁定范围应该尽量小,锁定- 范围越大效率越差
- 锁对象 - 一定要保证所有的线程都能够访问,如果代码中只有一个地方需要加锁大多都是用- self,这样可以避免单独创建一个锁对象
- 互斥锁 - 能够加锁任意NSObject对象
业务场景:1. 当任务比较简单比较小的任务时,选择
自旋锁. 2. 当任务比较庞大繁琐时,选择互斥锁
在使用属性时,我们用到了atomic和nonatomic,一个是线程安全的,另一个是不安全的,那么他们是加锁了么,接下来去研究下
atomic和nonatomic
- nonatomic是非原子属性,非线程安全,适合内存小的移动设备
- atomic是原子属性- (线程安全),针对多线程设计的默认值,- 保证同一时间只有一个线程能够写入,但是- 同一个时间多个线程都可以取值,这个特性也就是- 多读单写
- atomic本身就有一把锁(- 自旋锁)
- 我们知道属性在底层会去查找 - setter和- getter方法,在- objc4-812源码中搜索- objc_setProperty:

核心方法是reallySetProperty方法,然后进入查看:

- 当 - atomic时,会调用- PropertyLocks加一个- spinlock_t类型的自旋锁,所以- atomic是一个- bool值不是锁
- iOS开发的建议- 所有的属性都声明为 - nonatomic
- 尽量避免多线程抢夺同一资源 
- 尽量讲加锁、资源抢夺的业务逻辑交给服务端处理,减小移动客户端的压力 
 
线程和runloop关系
- runloop与线程是⼀⼀对应的,⼀个- runloop对应⼀个核⼼的线程,为什么说是核⼼的,是因为- runloop是可以嵌套的,但是核⼼的只能有⼀个,他们的关系保存在⼀个全局的字典⾥
- runloop是来管理线程的,当线程的- runloop被开启后,线程回在执行完任务后进入休状态,有了任务就会被唤醒去执行任务
- runloop在第一次获取时被创建,在线程结束时被销毁
- 对于 - 主线程来说,- runloop在程序一启动就- 默认创建好了
- 对于子线程来说, - runloop是- 懒加载的,只有当我们使用的时候才会创建,所以在子线程时用定时器要注意:确保子线程的- runloop被创建,不然定时器就不会回调
多线程的实现方案
iOS多线程方案如下:
作者:无双3
 
		 随时随地看视频
随时随地看视频 
				 
				 
				 
				 
				