1. 线程和进程
1.1 线程和进程的定义
线程
- 线程是进程的
基本执行单元 ,一个进程的所有任务都在线程中执行。 - 进程要想执行任务,
必须 得有线程 ,进程至少要有一条线程 。 - 程序启动会
默认开启一条线程 ,这条线程被称为主线程 或 UI 线程
进程
- 进程是指在系统中正在运行的一个
应用程序 。 - 每个进程之间是
独立 的,每个进程均运行在其专用的且受保护的内存空间 内。 - 通过“活动监视器”可以查看 Mac 系统中所开启的进程。
1.2 线程和进程的关系
地址空间 :同一进程的线程共享 本进程的地址空间,而进程 之间则是独立 的地址空间。资源拥有 :同一进程内的线程共享本进程的资源如内存、I/O、cpu等,但是进程之间的 资源是独立的。- 一个
进程崩溃 后,在保护模式下不会对其他进程产生影响 ,但是一个线程崩溃整个进程都死 掉。所以多进程要比多线程健壮 。 进程切换 时,消耗的资源大 ,效率高 。所以涉及到频繁的切换 时,使用线程要好于进程 。同样如果要求同时进行 并且又要共享某些变量 的并发操作,只能用线程 不能用进程- 执行过程:每个独立的进程有一个程序运行的
入口 、顺序执行序列 和程序入口。但是 线程不能独立执行 ,必须依存在应用程序 中,由应用程序提供多个线程执行控制 。 - 线程是
处理器调度 的基本单位 ,但是进程不是。 线程没有地址空间 ,线程包含在进程地址空间 中
2. 多线程
2.1 多线程的意义
优点
- 能适当提高程序的执行效率
- 能适当提高资源的利用率(CPU,内存)
- 线程上的任务执行完成后,线程会自动销毁
缺点
- 开启线程需要占用一定的内存空间(默认情况下,每一个线 程都占 512 KB)
- 如果开启大量的线程,会占用大量的内存空间,降低程序的性能
- 线程越多,CPU 在调用线程上的开销就越大, 每个线程被调度的次数会降低,线程的执行效率降低
- 程序设计更加复杂,比如线程间的通信、多线程的数据共享
2.2 多线程的原理
-
对于单核CPU,同一时间,CPU只能处理一条线程,即同一时间只有 1 个线程在执行, -
iOS中的多线程同时执行的本质是 CPU具有调度的能力,能够在多个任务直接进行快速的切换,由于CPU调度线程的时间足够快,就造成了多线程的“同时”执行的效果。CPU在多个任务直接进行快速的切换的时间间隔就是时间片。 -
如果是多核CPU就真的可以同时处理多个线程了,也就是并发。 多线程消耗:
2.3 多线程技术方案
方案 | 简介 | 语言 | 现场生命周期 | 使用频率 |
---|
pthread | 一套通用的多线程api 适用于Unix/ Linux / windows等系统 跨平台,可移植 使用难度大 | C | 程序员管理 | 几乎不用 | NSThread | 使用更加面向对象 简单易用,可直接操作线程对象 | OC | 程序员管理 | 偶尔使用 | GCD | 旨在替代NSThread等线程技术 充分利用设备多核 | C | 自动管理 | 经常使用 | NSOperation | 基于GCD 比GCD多了一些更简单实用的功能 使用更加面向对象 | OC | 自动管理 | 经常使用 |
2.3 多线程生命周期
线程的五个状态
- 新建:主要是实例化线程对象
- 就绪:线程对象调用start方法,将线程对象加入可调度线程池,等待CPU的调用,即调用start方法,并不会立即执行,进入就绪状态,需要等待一段时间,经CPU调度后才执行,也就是从就绪状态进入运行状态
- 运行:CPU负责调度可调度线城市中线程的执行,在线程执行完成之前,其状态可能会在就绪和运行之间来回切换,这个变化是由CPU负责,开发人员不能干预。
- 阻塞:当满足某个预定条件时,可以使用休眠,即sleep,或者同步锁,阻塞线程执行。当sleep到时,会获取同步锁重新将线程加入可调度线程池中。
- 死亡:分为两种情况 1. 正常死亡:即线程执行完毕。2. 非正常死亡:当满足某个条件后,在线程内部(或者主线程中)终止执行(调用exit方法等退出)
2.4 线程池原理
饱和策略
- AbortPolicy 直接抛出RejectedExecutionExeception异常来阻止系统正常运行
- CallerRunsPolicy 将任务回退到调用者
- DisOldestPolicy 丢掉等待最久的任务
- DisCardPolicy 直接丢弃任务
这四种拒绝策略均实现了RejectedExecutionHandler接口
3. 面试题
3.1 任务的执行速度的隐形因素
3.2 优先级反转
在看优先级反转前先了解什么是IO密集型线程和CPU密集型线程。
- IO密集型线程:频繁等待的线程,等待的时候会让出时间片。
- CPU密集型线程:很少等待的线程,意味着长时间占用着 CPU。
IO密集型线程比CPU密集型线程更容易得到优先级提升。 特殊场景下,当多个优先级都比较高的CPU 密集型线程霸占了所有 CPU 资源,而此时优先级较低的 IO 密集型线程将持续等待,产生线程饿死的现象。当然,为了避免线程饿死,CPU会发挥调度作用去逐步提高被“冷落”线程的优先级(提高优先级不一定会立即执行),IO 密集型线程通常情况下比 CPU 密集型线程更容易获取到优先级提升。
线程的优先级影响因素:
用户指定线程优先级:
4. 线程安全问题
多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题。
IOS主要有两种锁:
- 互斥锁
- 自旋锁 :当发现其他线程执行,当前线程询问,忙等,消耗性能比较高。任务短的时候用自旋锁,atomic的实现就用的自旋锁。
互斥锁:
- 保证锁内的代码,同一时间,只有一条线程能够执行。
- 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差。
- 当发现其他线程执行,当前线程休眠(就绪状态),一直在等打开,唤醒执行。
互斥锁参数 - 能够加锁的任意 NSObject 对象
- 锁对象一定要保证所有的线程都能够访问
- 如果代码中只有一个地方需要加锁,大多都使用 self,这样可以避免单独再创建一个
锁对象
自旋锁:
- 当发现其他线程执行,当前线程询问,忙等,消耗性能比较高。任务短的时候用自旋锁,atomic给setter 和getter 加锁加的就是自旋锁。
5. GCD
GCD 全称是 Grand Central Dispatch,主要做的是将任务添加到队列,并且指定执行任务的函数。纯 C 语言,提供了非常多强大的函数,。
- GCD 是苹果公司为多核的并行运算提出的解决方案
- GCD 会自动利用更多的CPU内核(比如双核、四核)
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
5.1 同步 & 异步
- 异步
dispatch_async :不用等待当前语句执行完毕,就可以执行下一条语句,会开启线程执行 block 的任务,异步是多线程的代名词。异步也是按顺序执行多项任务,但是是放在多个线程里同时运行,执行结束的顺序是随机的、不可预估的。总耗时大约是耗时最长的那项任务所消耗的时间。 - 同步
dispatch_sync : 必须等待当前语句执行完毕,才会执行下一条语句,不会开启线程,在当前执行 block 的任务。执行结束的顺序是固定的、和任务的执行顺序相同。总耗时是所有任务耗时之和。
5.2 串行 & 并行
- 串行:多个任务时,各个任务按顺序执行,完成一个之后才能进行下一个,遵守FIFO。
- 并行:指的是多个任务可以同时执行。
5.3 同步异步耗时对比
这里耗时相差大的原因是,同步会阻塞当前线程,先执行testMehod()之后才执行NSLOG()。而异步则大概率先NSLog在执行testMehod()。
面试题
下列打印顺序是怎么样的呢? 这里是并行队列,大概率的打印顺序是 1 - 5 - 2 - 3 - 4,也可能是 1 - 2 - 3 - 4 - 5。因为async不会阻塞当前线程,那么 2 和 5的打印顺序就不确定, 而 3 是sync,阻塞当前线程,那么就一定会在4之前打印。 这个代码会死锁,为什么呢?因为这里是串行队列, dispatch_sync等待dispatch_async执行完,才会执行,而dispatch_async则被dispatch_sync堵塞住,执行不完,这样就造成了死锁。
|