Swift学习47:多线程
前言
为什么几乎所有的GUI框架都是单线程的?:处理问题代价大于收益
多线程可以做什么
- 网络请求
- IO:读写文件
- 计算
- 数据模型转换
- ….
多线程编程的方式
- Thread
- Operation 和 OperationQueue
- GCD
Thread
- 三种中最轻量级的,自己管理线程的生命周期和线程同步
- 线程同步对数据加锁有一定的系统开销
- 快捷方式创建
- detachNewThread(_ block: @escaping @Sendable () -> Void)
1 | import Foundation |
- detachNewThreadSelector(_ selector: Selector, toTarget target: Any, with argument: Any?)
- 初始化器
- Thread(target: selector: , object: )
1 | import Foundation |
Operation 和 OperationQueue 概述
- 面向对象
- Operation + OperationQueue
- 取消、依赖、任务优先级、复杂逻辑、保存业务状态、子类化
Operation
- Operation:抽象类,任务
- BlockOperation
- 状态:
- isReady:准备好了,可以执行了
- isExecuting:执行中
- isFinished:执行完成
- isCancelled:取消执行
- 同步、异步
- sync
- async
- 添加依赖
OperationQueue
OperationQueue
队列,可以加入很多Operation
,- 底层使用
GCD
maxConcurrentOperationCount
:可以设置最大并发数defaultMaxConcurrentOperationCount
:根据当前系统条件动态确定的最大并发数,建议使用这个- 可以取消所有的
Operation
,但是当前正在执行的不会取消 - 所有
Operation
执行完毕后退出销毁
BlockOperation
1 | class ObjectOperation { |
由打印结果可知,异步执行了
继承 Operation
1 | class ObjectOperation { |
Operation 完成的回调
- completionBlock
1 | operation.completionBlock = { () -> Void in |
GCD
- 任务 + 队列
- 易用
- 效率
- 性能
GCD功能
- 创建管理Queue
- 提交Job
- Dispatch Group
- 管理Dispatch Object
- 信号量Semaphore
- 队里屏障Barrier
- Dispatch Source
- Queue Context数据
- Dispatch I/O Channel
- Dispatch Data 对象
GCD队列
- 主队列:任务在主线程执行
- 并行队列:任务会以先进先出的顺序入列和出列,但是因为多个任务可以并行执行,所以顺序是不一定的
- 串行队列:任务会以先进先出的顺序入列和出列,但是同一时刻只会执行一个任务
GCD-队列API
- Dispatch.main
- Dispatch.global
- DispatchQueue
- queue.label
- setTarget
GCD 基本操作
- sync
提交任务到当前队列里,并且直接任务执行完成,当前队列才会返回
- async
调用一个任务去立即执行,但是不用等任务执行完当前队列就会返回
- asyncAfter
调度一个任务多久之后去执行,但是不用等任务执行完当前队列就会返回
GCD 串行 & 并行
- 串行和并行描述的是任务之间如何运行
- 串行任务每一个仅执行一个
- 并行任务可以多个同时执行
GCD 同步 & 异步
GCD 使用
- 同步
1 | let queue = DispatchQueue(label: "myQueue",qos: DispatchQoS.default,attributes: DispatchQueue.Attributes.concurrent,autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit,target: nil) |
- 异步
1 | queue.async { // 异步 |
- asyncAfter
1 | queue.asyncAfter(deadline: .now() + 1) { |
GCD 源码剖析
dispatch_sync
线程A在串行队里dq中执行task1的过程中,如果再向dq中投递串行任务task2,同时还要求必须阻塞当前线程,等待task2结束(sync投递task2),那么这时候会发生死锁
因为这时候task1还没有结束,串行队列不会去执行task2,而我们又要在当前线程等待task2的结束才肯继续执行task1,即task1在等待task2,而task2也在等待task1,循环等待,形成死锁
1 | // 串行队列 |
临界区
一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码
竟态条件
- 两个或多个线程读写某些数据,而最后的结果取决于线程运行的精确时序
Locks
SpinLock
线程通过busy-wait-loop方式来获取锁,任何时刻只有一个线程能够获取锁,其他线程忙等待知道获取锁
临界区尽量简短,控制在100行代码以内,不要有限时或隐士的系统调用,调用的函数也尽量简短
保证访问锁的线程全部都处于同一优先级
@synchronized
1 | func synchronized(_ obj: Any, closure: ()->()) { |
- 只有传同样的对象给synchronized,才能起到加锁的作用
- 如果传nil,无法起到加锁的作用
- 可以重入
- synchronized不会持有传给它的对象
典型场景
一个页面有三个网络请求,需要在三个网络请求都返回的时候刷新页面
实现一个多线程安全的Array的读和写
编写一个多线程下载器,可以执行多个下载任务,每个任务可以保存当前下载字节数,总字节数,可以设置回调得到当前下载进度
需要在主线程等待一个异步任务返回,才继续执行下面的逻辑,但是又不希望堵塞用户事件
【场景一】:一个页面有三个网络请求,需要在三个网络请求都返回的时候刷新页面
【场景二】:实现一个多线程安全的Array的读和写
一个队列加方法方法
- 1、首先是
并行队列
,我们需要保持多线程并行操作 - 2、
sync
方法,封装读操作
,读操作在调用读方法时能直接拿到返回值,而不是异步获取 - 3、
async
方法使用barrier flag
,封装写操作
,起到一个栅栏的作用,等待所有的barrier flag
函数前操作执行完成后,barrier flag
函数之后的所有操作才执行
【场景三】:编写一个多线程下载器,可以执行多个下载任务,每个任务可以保存当前下载字节数,总字节数,可以设置回调得到当前下载进度
【场景四】:需要 在主线程等待一个异步任务返回,才继续执行下面的逻辑,但是又不希望堵塞用户事件
Promise 多线程编程模式
所谓的
Promise
,就是一个对象,用来传递异步操作的消息
。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的API
,可供进一步处理对象的状态不受外界影响。
Promise
对象代表一个异步操作,有三种状态:Pending
(进行中)、Resolved
(已完成)、Rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英文意思就是承诺
,表示其他手段无法改变一旦状态改变,就不会再变,任何时候都可以得到这个结果。
Promise
对象的状态改变,只有两种可能:从Pending
变为Resolved
变为Rejected
,只要这两种情况发送,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发送了,你再对Resolved
对象添加回调函数,也会立即得到这个结果。这与事件Event
完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的
Pipeline
将一个任务分解为若干个阶段(State),前阶段的输出作为下阶段的输入,各个阶段由不同的工作者线程负责执行
各个任务的各个阶段是并行(Parallel)处理的
具体任务的处理是串行的,即完成一个任务要一次执行各个阶段,但从整体任务上看,不同任务的各个阶段的执行时并行的
Master/Slave
将一个任务分解为若干个语义等同的子任务,并由专门的工作者线程来并行执行这些子任务,既提高计算效率,又实现了信息隐藏
- Post title:Swift学习47:多线程
- Post author:张建
- Create time:2023-03-12 22:52:33
- Post link:https://redefine.ohevan.com/2023/03/12/Swift课程/Swift学习47:多线程/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.