线程与进程
线程与进程概念
-
进程
是指在系统中正在运行
的一个应用程序
。 -
线程
是进程
的基本执行单元
,一个进程的所有任务都在线程中执行。 -
进程
中至少
要有一条线程
。 - 程序启动会默认开启一条
线程
,这条线程被称为主线程
或者UI线程
-
进程
之间是独立的
,每个进程均运行在其专用的且受保护的内存空间内。
进程与线程的关系
- 进程与线程是包含与被包含的关系,即:
进程中包含多个线程
。 - 同一个进程中的
线程共享进程的地址空间和资源
。 -
多进程比多线程健壮
。即一个进程崩溃之后一般情况下不会对其它进程产生影响,但是一个线程崩溃了,那整个进程都无法继续了。 -
CPU调度的基本单位是线程
。 -
各进程进行切换时,会消耗一定的资源
,如果频繁切换进程,效率较低下。 -
线程不能独立执行
,必须依存于应用程序中。
线程和Runloop的关系
-
runloop与线程是一一对应的
,一个runloop对应一个线程。 -
runloop是来管理线程的
,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。 -
runloop在第一次获取时被创建,在线程结束时被销毁
。 - 对于
主线程
来说,runloop在程序一启动就默认创建好了
。 - 对于
子线程
来说,runloop是懒加载的
,只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调
。
多线程原理
多线程原理
对于单核CPU,同一时间,CPU只能处理一条线程,即只有一条线程在工作,这种情况下的多线程的本质是CPU在多个任务之间进行快速的切换,由于CPU调度线程的时间足够快,就造成了多线程的“同时”执行的效果
。其中切换的时间间隔就是时间片。
对于多核CPU,同一个时间,CPU可以处理多条线程。
多线程意义
优点:
- 能适当提高程序的执行效率
- 能适当提高资源的利用率,如CPU、内存
- 线程上的任务执行完成后,线程会自动销毁
缺点:
- 开启线程需要占用一定的内存空间,默认情况下,每一个线程占用512KB
- 线程越多,CPU在调用线程上的开销就越大
- 程序设计更加复杂,比如线程间的通信,多线程的数据共享
多线程生命周期
多线程的生命周期有5个状态:
- 新建:
创建一个线程
,主要是实例化线程对象。 - 就绪:线程对象调用start方法,将
线程对象加入可调度线程池,等待CPU的调用
。即调用start方法,并不会立即执行,进入就绪状态,需要等待一段时间,经CPU调度后才执行,也就是从就绪状态进入运行状态。 - 运行:CPU负责调度
可调度线程池
中线程的执行,在线程执行完成之前,其状态可能会在就绪和运行之间来回切换,这个变化是由CPU负责,开发人员不能干预。 - 阻塞:当满足某个预定条件时,如sleep或者同步锁,这些会阻塞线程执行。当进入sleep时,会重新将线程置为就绪状态。
- 死亡:分为两种情况:
- 情况1:正常死亡,即线程执行完毕。
- 情况2:非正常死亡,即当满足某个条件后,在线程内部(或者主线程中)终止执行(调用exit方法等退出)
线程池原理
线程池原理.png-
【第一步】判断核心线程池中是否已满
如果已经满了,进入【第二步】。
如果未满,则创建新的线程执行任务。 -
【第二步】判断线程池工作队列是否已经满
如果已经满了,进入【第三步】。
如果未满,则将任务存储到工作队列中,等待CPU的调度。 -
【第三步】判断线程池中的线程是否都处于执行状态
如果是,进入【第四步】。
如果不是,则安排可调度线程池中空闲的线程去执行任务 -
【第四步】交给饱和策略去执行,主要有以下四种(在iOS中并没有找到以下4种策略)
AbortPolicy
:直接抛出RejectedExecutionExeception异常来阻止系统正常运行
CallerRunsPolicy
:将任务回退到调用者
DisOldestPolicy
:丢掉等待最久的任务
DisCardPolicy
:直接丢弃任务
iOS中多线程的实现方案
方案 | 简介 | 语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 1、一套能用的多线程API; 2、适用于Linux、Unix、Windows等系统; 3、跨平台、可移植; 4、使用难度较大 |
C | 程序员管理 | 几乎不用 |
NSThread | 1、使用更加面向对象; 2、使用较为简单,可直接操作线程对象; |
OC | 程序员管理 | 偶尔使用 |
GCD | 1、替代NSThread等线程技术; 2、充分利用设备的多核; |
C | 自动管理 | 经常使用 |
NSOperation | 1、基于GCD封装; 2、比GCD多了一些更加简单实用的功能; 3、使用更加面向对象; |
OC | 自动管理 | 经常使用 |
在iOS的多线程使用过程中,存在C和OC的桥接,则需要注意的点如下:
-
__bridge
只做类型转换,但是不修改对象(内存)管理权 -
__bridge_retained
(也可以使用CFBridgingRetain)将Objective-C的对象转换为 Core Foundation的对象,同时将对象(内存)的管理权交给我们,后续需要使用 CFRelease或者相关方法来释放对象 -
__bridge_transfer
(也可以使用CFBridgingRelease)将Core Foundation的对象 转换为Objective-C的对象,同时将对象(内存)的管理权交给ARC。
线程安全问题
当多个线程同时访问同一块资源时,容易引发数据错乱和数据安全问题,目前主要有以下两种解决方案
-
互斥锁(即同步锁)
:@synchronized 自旋锁
互斥锁
作用:确保同一时间,只有一条线程能够执行
-
如果代码中只有一个地方需要加锁,大多都使用self,这样可以避免单独再创建一个锁对象
-
加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入
休眠状态
-
互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差
-
互斥锁能够加锁任意 NSObject 对象
-
锁对象一定要保证所有的线程都能够访问
自旋锁
自旋锁与互斥锁类似,但它不是通过休眠使线程阻塞,而是在获取锁之前一直处于忙等
(即原地打转,称为自旋)阻塞状态。
使用场景:锁持有的时间短,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符atomic
,就有一把自旋锁。
加入了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能。
atomic 原子锁 & nonatomic 非原子锁
atomic
和nonatomic
主要用于属性的修饰。
-
atomic是原子属性
,是为多线程开发准备的,是默认属性。它会在属性的setter
方法中,增加锁(自旋锁)
,保证同一时间,只有一条线程对属性进行写操作
,但同一时间,可以多条线程对属性进行读操作
,它在Mac开发中较为常用。 -
nonatomic是非原子属性
。它不会增加锁,因此性能较高,它在移动端开发较为常用。
网友评论