1.线程与进程的关系
线程:
线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
- 进程要向执行任务,必须得有进程,进程至少要有一条线程
- 程序启动会
默认开启一条线程
,这条线程被称为主线程
或UI线程
进程:
- 进程是指在系统中正在运行的一个应用程序
- 每个进程之间是
独立的
,每个进程均运行在其专用的且受保护
的内存空间内
- 通过“活动监视器”可以查看Mac系统中所开启的进程
地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
资源拥有:同一进程的线程共享本进程的资源,而进程之间则是独立的资源。
2.多线程
优点:
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(CPU,内存)
- 线程上的任务执行
完成后
,线程会自动销毁
缺点:
- 开启线程需要占用一定的内存空间(
默认情况下,每一个线程都占512KB
) - 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU在调用线程上的开销就越大
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享
iOS 的多线程其实就是CPU快速的在多个线程之间快速的切换,造成了多线程同事执行的效果。
如果线程数非常多就会消耗大量的CPU资源,导致线程执行效率降低。
3.多线程生命周期(掌握)
先来看一张图:引用Style_月月
![](https://img.haomeiwen.com/i18769452/9114e99bece7e9bc.png)
从图中可以看出多线程的生命周期
分为:
新建 -> 就绪 -> 运行 -> 阻塞 -> 死亡 -> 就绪
|________________|
-
新建:实例化线程对象
-
就绪:线程调用start方法,将线程加入可调度线程池,
等待CPU的调用
。不会立刻执行,当CPU调度到该线程时状态由就是转为运行
。 -
运行:CPU负责调度可调度线程池中线程的执行,在线程执行完成之前,其状态可能会在就绪和运行之间来回切换,这个变化是由CPU负责,开发人员不能干预。
-
阻塞:当满足某个预定条件时,可以使用休眠,即
sleep
,或者同步锁
,阻塞线程执行。当进入sleep时,会重新将线程加入就绪中。下面关于休眠的时间设置,都是NSThread的 -
死亡:正常死亡,即线程执行完毕;非正常死亡,调用exit方法等退出
4.线程和Runloop的关系
- runloop与线程是⼀⼀对应的,
⼀个runloop对应⼀个核⼼的线程
,为什么说是核⼼的,是因为runloop是可以嵌套的,但是核⼼的只能有⼀个
,他们的关系保存在⼀个全局的字典⾥。 -
runloop是来管理线程的
,当线程的runloop被开启后,线程会在执⾏完任务后进⼊休眠状态,有了任务就会被唤醒去执⾏任务。 - runloop在第⼀次获取时被创建,在线程结束时被销毁。
- 对于
主线程
来说,runloop在程序⼀启动就默认创建好了
。 - 对于
⼦线程
来说,runloop是懒加载的
,只有当我们使⽤的时候才会创建,所以在⼦线程⽤定时器要注意:确保⼦线程的runloop被创建,不然定时器不会回调。
线程的
exit
和cancel
说明
- exit:强行终止线程,后续的所有代码不执行
- cancel:取消
当前
线程,但是不能取消正在执行的线程
面试:线程的优先级越高,任务执行越快?
答: 否,线程执行的快慢,取决于任务的复杂程度
,以及CPU的调度
情况。
5.线程池(掌握)
线程池运行流程如下图![](https://img.haomeiwen.com/i18769452/7210e139d5e28bb0.png)
- 核心线程是否都在执行任务
- NO, 创建新的线程去执行
- YES, --->2
- 线程池工作列队是否饱满
- NO,将任务储存在工作队列
- YES, --->3
- 判断线程池中的线程是否都处于执行状态
- NO,安排线程去执行任务
- YES, --->4
- 交给饱和策略去执行
饱和策略
-
AbortPolicy
直接抛出RejectedExecutionExeception异常来阻⽌系统正常运⾏ -
CallerRunsPolicy
将任务回退到调⽤者 -
DisOldestPolicy
丢掉等待最久的任务 -
DisCardPolicy
直接丢弃任务
这四种拒绝策略均实现的RejectedExecutionHandler
接⼝
iOS中多线程的实现方案
![](https://img.haomeiwen.com/i18769452/0ac35607c321e650.png)
6.线程安全问题
多线程会可能触发抢夺资源的问题(数据安全问题)
iOS提供了两种解决方案(当然还有其他的方法,递归锁、条件锁..)
互斥锁
-
互斥锁
@synchronized
- 保证锁内的代码,
同⼀时间,只有⼀条线程能够执⾏
!如果发现其他线程正在执行锁定的代码,新线程就会进入休眠
- 互斥锁的
锁定范围,应该尽量⼩
,锁定范围越⼤,效率越差!
互斥锁参数
能够加锁的任意 NSObject 对象
- 注意:
锁对象⼀定要保证所有的线程都能够访问
- 如果代码中只有⼀个地⽅需要加锁,⼤多都使⽤
self
,这样可以避免单独再创建⼀个锁对象
- 保证锁内的代码,
自旋锁
-
自旋锁与互斥锁类似,但它是在获取锁之前一直处于忙等(即原地打转,称为自旋)阻塞状态
-
使用场景:锁持有的
时间短
,就需要使用自旋锁
,属性修饰符atomic
,本身就有一把自旋锁
atomic与nonatomic 的区别
nonatomic ⾮原⼦属性
atomic 原⼦属性(线程安全)
,针对多线程设计的,默认值
- 保证同⼀时间只有
⼀个线程能够写⼊
(但是同⼀个时间多个线程都可以取值
) - atomic 本身就有⼀把锁(⾃旋锁)
-
单写多读
:单个线程写⼊,多个线程可以读取
atomic:线程安全,需要消耗⼤量的资源
nonatomic:⾮线程安全,适合内存⼩的移动设备
iOS 开发的建议
所有属性都声明为 nonatomic
尽量避免多线程抢夺同⼀块资源
尽量将加锁、资源抢夺的业务逻辑交给服务器端处理
,减⼩移动客户端的压⼒
网友评论