前言
iOS多线程主要库有GCD,Operation。网上也有很多介绍的文章。但更多只是停留在表面,API的使用。很少有文章能从源码角度分析Operation,OperationQueue。
本文的Operation源码来自苹果的Operation源码版本 2020_Feb_16 。版本最新。如果链接失效,可以去swift-corelibs-foundation里面找找。有时候版本更新,路径会发生改变。
本文将以一问一答的形式为大家展示Operation源码细节。Let's go!
Operation 十问
Operation
同步Operation使用教程
重写main()方法即可。
源码:简单理解,OperationQueue调用Operation其实就调用了Op的start
方法。
Op的start
方法,做了些属性初始化,然后调用了main
方法。
internal func _schedule(_ op: Operation) {
op._state = .starting
// set current tsd
OperationQueue._currentQueue.set(self)
op.start()
OperationQueue._currentQueue.clear()
// unset current tsd
if op.isFinished ... {
Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: op)
}
}
题外话:
tsd: thread-local storage。线程特有数据。方便op的main函数执行时,知道自己处于哪个OperationQueue。
异步Operation使用教程
参考该Blog即可。
Asynchronous operations for writing concurrent solutions in Swift
总结:重写
- isExecuting
- isFinished
- isAsynchronous
- start()
为什么异步Operation这么使用
重写start()的必要性
源码start发生了什么?
1.start里面调用了main
2.运行完main,_state = finishing,发生属性变更信号。
⚠️不重写start,意味着自动调用start后,改op会自动finish。
我们的目标是异步Operation,执行完main,还要几秒才结束Operation。finishing信号需要我们自己控制。
open func start() {
let state = _state
let isCanc = _isCancelled
if !isCanc {
_state = .executing
// _execute 函数里面就是调用了main()
_queue?._execute(self) ?? main()
}
if __NSOperationState.executing == _state {
_state = .finishing
Operation.observeValue(forKeyPath: _NSOperationIsExecuting, ofObject: self)
Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: self)
} else {
_state = .finishing
Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: self)
}
}
open func main() { }
再看看源码的isExecuting,isFinished。
open var isExecuting: Bool {
return __NSOperationState.executing == _state
}
open var isFinished: Bool {
return __NSOperationState.finishing.rawValue <= _state.rawValue
}
结论:重写这些属性,是为了手动控制异步Operation的生命周期。而不是跑完main函数,op就finish了。finish属性很关键!后面就会提到。
Operations之间的依赖如何实现
_addDependency
这里引入两个数组。
-
__dependencies
-
__downDependencies
__dependencies
B.addDependency(A) | B.addDependency(A1) | B.addDependency(A2)
B依赖了3个Op,__dependencies就是[A2,A1,A],DependencyCount = 3.
_addParent
OperationsB 依赖 OperationsA
OperationsB1 依赖 OperationsA
OperationsB2 依赖 OperationsA
Parent就是往__downDependencies数组里面添加,A的__downDependencies
数组是[B2,B1,A]。
总结:B.addDependency(A),把A添加进B依赖的数组。把B添加进A的被依赖数组。observeValue是发送ready信号,可能会触发queue的运行。
源码:
internal func _addDependency(_ op: Operation) {
var up: Operation?
// 把A添加进依赖数组。
if __dependencies.first(where: { $0 === op }) == nil {
__dependencies.append(op)
up = op
}
// upwards此刻为A。
if let upwards = up {
let upIsFinished = upwards._state == __NSOperationState.finished
if !upIsFinished && !_isCancelled {
assert(_unfinishedDependencyCount >= 0)
_incrementUnfinishedDependencyCount()
upwards._addParent(self)
}
}
Operation.observeValue(forKeyPath: _NSOperationIsReady, ofObject: self)
}
OperationsA执行完,运行OperationsB
OperationsA 运行完毕。
源码会有一句。包括我们自定义的异步Op。执行完毕都需要出发observeValue。
源码:
open func start() {
main()
_state = .finishing
Operation.observeValue(forKeyPath: _NSOperationIsExecuting, ofObject: self)
Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: self)
}
自定义异步Op
private var _isFinished: Bool = false
override private(set) var isFinished: Bool {
get {
return lockQueue.sync { () -> Bool in
return _isFinished
}
}
set {
willChangeValue(forKey: "isFinished")
lockQueue.sync(flags: [.barrier]) {
_isFinished = newValue
}
didChangeValue(forKey: "isFinished")
}
}
isFinished 触发了什么
B.addDependency(A) | B.addDependency(A1) | B.addDependency(A2)。
如果A,A1,A2都没有执行完毕,UnfinishedDependencyCount = 3.(UnfinishedDependencyCount是在之前依赖的时候进行添加的。上面有细节。
- A完成了,UnfinishedDependencyCount-1 = 2.
- A1完成了,UnfinishedDependencyCount-1 = 1.
- A2完成了,UnfinishedDependencyCount 本来就是1,没有再等待的Op,ready去执行操作。
重点:A的isFinished减少那些依赖于它(比如B)的UnfinishedDependencyCount。遍历downDependencies
数组,获取Op。
如果B的UnfinishedDependencyCount = 1,说明A是B的最后一个依赖,B触发observeValue,ready。ready意味着执行main函数。
barrier Operation
barrier Operation 调用时机的实现
比如OperationQueue里现在有op1-op10 10个Operation。此刻往里面添加barrier Operation。 barrier Operation 会addDependency (op1-op10)。这样,barrier Operation就可以保证在10个Operation都执行完毕后再执行了。
barrier Operation 是如何实现阻拦其他Operation的
这个和上面的实现方式没有区别,所有后加入的Op,Operation.addDependency(barrier Operation)。barrier Operation就能起到阻拦作用。
OperationQueue
Operations的优先级调度如何实现
OperationQueue的7条链表
首先说明OperationQueue有7条链表。
firstOperation,lastOperation存放所有被添加到OperationQueue中的Operation。(细节:不包括barrier Operation)
firstPriorityOperation,lastPriorityOperation存放不同优先级的链表的开头,和结尾。
var __firstOperation: Unmanaged<Operation>?
var __lastOperation: Unmanaged<Operation>?
var __firstPriorityOperation: (barrier: Unmanaged<Operation>?,
veryHigh: Unmanaged<Operation>?,
high: Unmanaged<Operation>?,
normal: Unmanaged<Operation>?,
low: Unmanaged<Operation>?,
veryLow: Unmanaged<Operation>?)
var __lastPriorityOperation: (barrier: Unmanaged<Operation>?,
veryHigh: Unmanaged<Operation>?,
high: Unmanaged<Operation>?,
normal: Unmanaged<Operation>?,
low: Unmanaged<Operation>?,
veryLow: Unmanaged<Operation>?)
调度
去掉大量的噪音。简化代码。
从高优先级开始遍历,获取该优先级的链表,执行链表的op。
这样,高优先级的就会比低优先级的先执行。
internal func _schedule() {
for prio in Operation.QueuePriority.priorities {
var op = _firstPriorityOperation(prio)
while let operation = op? {
queue.async(execute: op.schedule)
op = next
}
}
}
OperationQueue如何实现并发量的控制
slotsAvail 和 _suspended决定是否继续调用Op。
如果已经达到最大并发,或者当前queue已经停止了。不在派发Op。
internal func _schedule() {
var slotsAvail(可并发数) = 最大并发数 - 正在执行的op数
for prio in Operation.QueuePriority.priorities {
var op = _firstPriorityOperation(prio)
while let operation = op? {
if 0 >= slotsAvail || _suspended {
break
}
queue.async(execute: op.schedule)
op = next
}
}
}
OperationQueue,Operation用了哪些GCD的API
WorkItem
DispatchWorkItem:Operation的start都会被封装进WorkItem
其实OperationQueue调用Operation的方式是调用WorkItem。
Operation属性__schedule
var __schedule: DispatchWorkItem?
DispatchQueue派发Operation操作。
if let schedule = operation.__schedule {
if operation is _BarrierOperation {
queue.async(flags: .barrier, execute: {
schedule.perform()
})
} else {
queue.async(execute: schedule)
}
}
封装WorkItem 的内部
封装的原因有两个:
- op调用start时,可以知道自己所处的queue。通过
OperationQueue.current
.(源码tsd处) - 控制串型Op的finish信号
internal func _schedule(_ op: Operation) {
op._state = .starting
// set current tsd
OperationQueue._currentQueue.set(self)
op.start()
OperationQueue._currentQueue.clear()
if op.isFinished && op._state.rawValue ... {
Operation.observeValue(forKeyPath: _NSOperationIsFinished, ofObject: op)
}
}
DispatchQueue
用于派发任务,异步调用WorkItem。
if let schedule = operation.__schedule {
if operation is _BarrierOperation {
queue.async(flags: .barrier, execute: {
schedule.perform()
})
} else {
queue.async(execute: schedule)
}
}
waitUntilFinished 的使用和实现
OperationQueue func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool) {
_addOperations(ops, barrier: false)
if wait {
for op in ops {
op.waitUntilFinished()
}
}
}
---
operation func waitUntilFinished() {
__waitCondition.lock()
while !isFinished {
__waitCondition.wait()
}
__waitCondition.unlock()
}
waitUntilFinished = YES,就是让所有的Op进入wait()状态。也就是当前线程加了9个锁。
unlock:
observeValue 的finish中,isFinished = YES。所有Op执行完毕,当前线程可以继续运行。
op.__waitCondition.lock()
op.__waitCondition.broadcast()
op.__waitCondition.unlock()
OperationQueue 的内部任务派发机制
从Operation添加到被执行,中间发生了什么?
添加到队列
- Operation依据Qos被封装成DispatchWorkItem,作为自己的属性,方便到时候的派发。
- Operation依据Qos等级,被分配到对应优先级的链表。(前面提到过,不同优先级Op有不同优先级的链表)
执行
- OperationQueue 并发分配任务,从优先级高的链表开始调度。
- 没有超过最大并发数,异步执行被封装的DispatchWorkItem。
Blog
Asynchronous operations for writing concurrent solutions in Swift
WWDC
源码版本
个人修改注释版,直接把源码拷贝过来,改了名字。但运行起来会崩溃,没找到原因。但跑起来,也足够研究里面的90%逻辑了。记住,YJOperationQueue和YJOperation搭配使用。