多线程
同步、异步 和 串行、并行/并发
在正式开始前,先回顾一下这四者的区别!
-
同步:多个任务情况下,一个任务A执行结束,才可以执行另一个任务B。只存在一个线程。
-
异步:多个任务情况下,一个任务A正在执行,同时可以执行另一个任务B。任务B不用等待任务A结束才执行。存在多条线程。
并行和并发是异步的两种形式
-
并行:并行才是真正的异步,多核CUP可以同时开启多条线程供多个任务同时执行,互不干扰,如上图的并行,其实和异步图例一样。
-
并发:其实是伪异步,在单核CUP中只能有一条线程,但是又想执行多个任务。这个时候,只能在一条线程上不停的切换任务,让用户以为是在同时执行
进程和线程
进程:一个正在运行的应用程序
线程:一个进程(应用程序)至少有一条线程,进程的所有任务都在线程中执行
线程的串行
-
一个线程中任务的执行是串行的
-
如果一个线程中执行多个任务,那么只能一个个按照顺序执行
线程和进程的区别
- 线程是CPU调用(执行任务的最小单位)
- 进程是CPU分配资源和调度的单位(CPU是不会分配资源给线程的)
- 一个程序可以对应多个进程,一个进程中可以有多个线程,但至少有一个线程
- 同一个进程内的线程共享进程的资源
其实可以把进程比作一个工厂,而进程就是工厂里的工人
多线程
刚刚上面说到了,一个进程中至少要有一条线程,那么多条线程即多线程,每条线程可以并行(同时)执行不同的任务,可以提高程序的执行效率
- 线程内部任务的执行是串行的
- 线程与线程之间任务的执行是可以并行的
原理
- 其实,在同一个时间,CPU只能处理一条线程
- 刚刚上面说的每条线程可以同时执行不同的任务,其实是CPU快速的在多条线程之间调度,而这过程非常快,因此造成了多线程并行的假象(这里自己再去注意并行和并发的区别)
- 所以,其实真正的多线程是并发的
思考:线程非常多时,会怎么样?
- CPU在调度线程时也需要消耗资源,如果线程非常多,会导致CPU的消耗极高
- 每条线程被调度执行的频次会降低(线程的执行效率降低)
多线程的优缺点
优点:
- 能适当提高程序的执行效率
- 能适当提高资源利用率(CPU、内存利用率) 缺点:
- 创建线程需要开销
- 如果开启大量的线程,会降低程序的性能
- 线程越多,CPU在调度线程的开销越大
- 程序设计更复杂:比如线程之间的通信、多线程的数据共享
主线程
一个iOS程序运行后,默认为开启一条线程,称为“主线程”或“UI线程”
主要作用
- 显示/刷新UI界面
- 处理UI事件(点击、滚动等) 注意:只要凡是和UI相关的事件,就必须在主线程中实现
注意点
-
较耗时操作不要放到主线程,若放到主线程,会导致界面卡顿
- 因为线程内部的执行是串行的
-
将耗时操作放在子线程(说法不一样,也可以叫后台线程、非主线程)
获取线程
- 获得主线程
NSThread *mainThread = [NSThread mainThread];
- 获取当前进程
NSThread *currentThread = [NSThread currentThread];
- 判断主线程
- 打印线程,若其number=1,那就是主线程
//2. 类方法
BOOL isMainThreadA = [NSThread isMainThread];
//3. 对象方法
NSThread *currentThread = [NSThread currentThread];
BOOL isMainThreadA = [currentThread isMainThread];
iOS中多线程的实现方案
创建和启动线程
一个NSThread对象就代表一条线程
-
方法一 需要创建和启动线程
还可以设置线程名称和线程优先级
-
方法二
-
方法三
后面两个方法创建线程虽然简单快捷,但是只有方法一可以拿到线程对象
NSThread线程的生命周期
你可能会有一个疑惑:在创建线程的函数中,一旦函数走到它自己的 } 时,函数里的局部变量便都会被释放,那么是如何在程序的其他部分取得该线程对象的呢?
这时候我们就要来思考一下NSThread线程的生命周期了~
当任务执行完毕之后,线程才会被释放
(一定要清楚线程的生命周期!)
线程的状态
就绪、执行、阻塞、死亡
- 阻塞:线程还在内存中,但是不在可调度线程池中
- 死亡:线程从可调度线程中移出到内存,再在内存中释放 (所以线程死亡后,你再用start方法是不能重新开启线程的,因为该线程已经不在内存中了)
多线程的安全隐患
- 资源共享
- 一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源
- 例如多个线程访问同一个对象、同一个文件... 那么就容易引发数据错乱和数据安全问题
这是一个典型的例子
解决方法——互斥锁
- 互斥锁必须是全局唯一的
- 互斥锁能有效防止因多线程抢夺资源造成的数据安全问题
- 会消耗大量的CPU资源
- 会导致线程同步
原子性和非原子性
- nonatomic:非原子属性,不会为setter加锁
- 非线程安全
- 适合内存小的移动设备
- atomic:原子属性,为setter方法加锁
- 线程安全
- 消耗资源
一般开发情况都用nonatomic
线程间的通信
- 数据传递
- 任务传递