并发 && 并行
多线程的同时执行并非"并行", 而是"并发", "并发"无论从宏观还是微观上都是同时执行的, 而"并行"宏观上是同时执行, 微观上仍然是一个cpu的在多条线程上的来回切换.
多线程解决了什么问题?
从第一个问题可以看出, cpu(单核)在多条线程之间轮询执行的效率必然要低于单线程执行. 在单核cpu的机器上单线程的效率是最高的. 所以多线程的出现并非是为了解决cpu的效率问题 (注意: 并非说多线程不能提高cpu的效率),而是为了解决"阻塞"问题, 如: task1 在 thread1 执行需要10秒, task2 在 thread2 需要1秒, 人们不愿意等到 task1 执行完成之后才能看到 task2 的执行结果, 也就是不愿意 task1 阻塞 task2 的执行. 可以说多线程是为了解决"阻塞"问题而生的.(多核cpu的问题涉及到硬件, 还不是很清楚, 不误导大家了)
线程的串行
线程是进程的执行单元/路径
一个线程中执行多个任务, 是按顺序一个个执行的
多线程的原理
CPU在各个线程中快速切换, 其调度线程的时间够快, 就产生了多线程的"并发"执行的假象
多线程优缺点
优点:
- 可以使任何需要及时响应的任务及时响应, 如用户UI可以在进行其它工作的同时一直处于活动状态.
- 可以设置线程优先级.
- 可以适当提高资源利用率, 如: 下载速度最大总共1m/s的网络环境下, 单线程下载 task1 && task2, 如果因为某些原因导致下载task1的速度只有100k/s, 那么下载完这2个任务的时间必然很长; 如果开启2条线程下载, 下载速度就可能达到1m/s, 如此大大提高了带宽的利用率.
缺点:
- 如果线程多每条线程被调度执行的频次会降低(线程的执行效率低).
- 线程占用内存, 主线程默认占1M, 子线程占用512k.
- 线程间通信和线程间数据共享, 资源抢夺造成程序的复杂性.
线程的状态
- 可调度线程池概念
系统底层用于管理线程的一个"池子", 装着所有可供系统调度的线程. - 线程的状态
- new
为线程分配内存空间 - runnable
线程进入可调度线程池 - running
线程有任务正在执行 - blocked
线程阻塞, 被移除可调度线程池 - dead
线程死亡, 系统回收内存
- new
线程安全问题
资源抢占
举例: 如果一份文件需要张三和李四签字, 可认为2人对应两条线程, 若二人同时执行签字, 可能最后签字结果是:张四李三, 李张三四等.
若要解决此问题, 必须保证文件在同一段时间被一个人占用, 另一个人要么先"睡会", 要么"干点别的", "睡会"和"干点别的"对应两种线程锁.-
锁
- 同步锁: @synchronized
用上面的例子, 张三在签字的过程中, 把文件''锁住'', 李四准备来签, 看到文件被锁, 会"睡等" - 自旋锁 OSSpinLock
用上面的例子, 张三在签字的过程中, 把文件''锁住'', 李四准备来签, 看到文件被锁, 会"转圈等".
- 同步锁: @synchronized
两种锁的效率:
?对于同步锁,如果资源已经被占用,下一个调用者只能先进入睡眠状态等待。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,下一个调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名.
看上去 "自旋"会消耗大量资源, 效率更低. 实际情况是"睡"需要改变线程的状态, 会把线程从可调度线程池中取出, 从 runnable 状态到blocked 状态. 而"自旋"在做一个空循环, 不分配任何内存空间, 相比与改变线程的状态系统消耗的资源会少一些.
- 原子属性
atomic :消耗较多资源, 相对线程安全
nonatomic:非线程安全
线程间通信
在一个进程中, 线程往往不是孤立存在的, 多线程经常需要相互通信:
- 一个线程传递数据给另一个线程
- 在一个线程中执行完特定的任务后, 转到另一个线程继续执行
iOS中多线程的实现方案
技术 | 简介 | 语言 | 生命周期 |
---|---|---|---|
pthread | 跨平台Unix Linux Windows | C | 需要管理 |
NSThread | 面向对象 可直接操作线程对象 | OC | 需要管理 |
GCD | 中枢调度系统(在队列中调度任务到相应线程) | C | 系统管理 |
NSOperation | 对GCD的封装, 面向对象 | OC | 系统管理 |
- pthread
基本过时, 很少能看见iOS项目中有使用
NSThread
iOS中 NSThread 多用于的判断线程编号, 是否处于主线程等-
GCD
- 自动管理线程的生命周期 (创建线程, 调度任务), C,JAVA中需要关注
- 核心: 任务添加队列, 任务的取出遵循队列FIFO原则
在 GCD 中,一个同步函数只在完成了它预定的任务后才返回。
一个异步函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成.因此,一个异步函数 . 不会阻塞当前线程去执行下一个函数.
-
NSOperation
- 对GCD的封装, 是GCD的进一步抽象, NSOperation封装了需要执行的操作和执行操作所需的数据,让程序员面向对象开发.
- NSOperation本身是个抽象类,必须用其子类(系统提供子类, 也可以自定义子类)
- NSOperation 封装了一些方法, 方便设置依赖, 方便取消操作, 方便判断当前操作的状态等.
- 建议较为底层的, 公用的多线程??槭褂肗SOperation, 其效率虽比GCD略低, 不过其面向对象以及能实现继承的优点就足以让程序员们去使用.