文章目录
一、Operation简介
Operation是苹果提供给我们的一套多线程解决方案。实际上Operation是基于GCD更高一层的封装,完全面向对象。但是比GCD更简单、代码可读性也更高。
二、Operation操作、OperationQueue操作队列
- 操作(Operation):
执行操作,在线程中执行的那段代码。
在GCD中是放在block中的。在NSOperation中,我们使用NSOperation子类NSInvocationOperation、BlockOperation,或者自定义子类来封装操作 - 操作队列(Operation Queues)
不同于GCD中的调度队列FIFO(先进先出)的原则。OperationQueue对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(费结算执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。
OperationQueue为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程上,而自定义队列在后台执行。
三、Operation、OPerationQueue使用步骤
Operation需要配合OperationQueue来实现多线程。因为默认情况下,Operation单独使用时系统同步执行操作,配合OperationQueue我们能更好的实现异步执行。
Operation实现多线程的使用步骤分为三步:
- 创建操作:先将需要执行的操作封装到一个Operation对象中。
- 创建队列:创建OperationQueue对象。
- 将操作加入到队列中:将Operation对象添加到OperationQueue对象中。
之后,系统就会自动将OperationQueue中的Operation取出来,在新线程中执行操作。
四、常用属性和方法
1. Operation常用属性和方法
开启&取消操作方法
open func start(); 执行并发任务的入口
open func cancel(); 可取消操作,实质是标记 isCancelled 状态。
open func main(); 执行非并发任务的入口
优先级
open var threadPriority: Double; 指定优先级
open var qualityOfService: QualityOfService; 指定特殊服务质量等级
判断操作状态方法
open var isFinished: Bool { get }; 判断操作是否已经结束,即正常运行结束。。
open var isCancelled: Bool { get }; 判断操作是否已经标记为取消,如果cancel()方法被调用,那么Operation实例就会切换到这个状态。
open var isExecuting: Bool { get }; 判断操作是否正在在运行,发生在调用start()方法之后。
open var isReady: Bool { get }; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
操作同步
open func waitUntilFinished(); 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
open var completionBlock: (() -> Swift.Void)?; completionBlock 会在当前操作执行完毕时执行 completionBlock。
open func addDependency(_ op: Operation); 添加依赖,使当前操作依赖于操作 op 的完成。
open func removeDependency(_ op: Operation); 移除依赖,取消当前操作对操作 op 的依赖。
open var dependencies: [Operation] { get }; 在当前操作开始执行之前完成执行的所有操作对象数组。
3. OperationQueue常用属性和方法
取消/暂停/恢复操作
open func cancelAllOperations(); 可以取消队列的所有操作。
open var isSuspended: Bool; {get}判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。
open var isSuspended: Bool; {set}可设置操作的暂停和恢复,YES 代表暂停队,NO 为恢复状态。
操作同步
open func waitUntilAllOperationsAreFinished(); 阻塞当前线程,直到队列中的操作全部执行完毕。
添加/获取操作
open func addOperation(_ block: @escaping () -> Swift.Void); 向队列中添加一个 BlockOperation 类型操作对象。
open func addOperations(_ ops: [Operation], waitUntilFinished wait: Bool); 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
open var operations: [Operation] { get }; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。
open var operationCount: Int { get }; 当前队列中的操作数。
open var maxConcurrentOperationCount: Int; 最大并发数
open var qualityOfService: QualityOfService; 优先级
获取队列
open class var current: OperationQueue? { get }; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
open class var main: OperationQueue { get }; 获取主队列。
五、Operation的子类
Operation是个抽象类,不能用来封装操作。我们只有使用它的子类来封装操作。
1. NSInvocationOperation
/** 调用子类NSInvocationOperation */
- (void)callInvocationOperation {
// 1.创建 NSInvocationOperation 对象
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
// 2.调用 start 方法开始执行操作
[op start];
}
/** 任务1 */
- (void)task1 {
for (int i = 0; i < 2; i++) {
NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
}
}
输出结果:
使用NSInvocationOperation执行一个操作,操作是在当前线程执行,并没有开辟新线程。
注意在swift中因为类型安全,不能使用NSInvocationOperation
'NSInvocationOperation' is unavailable in Swift: NSInvocation and related APIs not available
2. BlockOperation
/** 使用子类NSBlockOperation */
private func useBlockOperation() {
// 1.创建 NSBlockOperation 对象
let op = BlockOperation.init {
for _ in 0..<2 {
print("2---\(Thread.current)")// 打印当前线程
}
}
// 2.调用 start 方法开始执行操作
op.start()
}
输出结果:
在没有使用OperationQueue、在主线程单独使用BlockOperation执行一个操作的情况下,操作是在当前线程执行的,并没有开辟新线程。
注意:和上边 NSInvocationOperation 使用一样。因为代码是在主线程中调用的,所以打印结果为主线程。如果在其他线程中执行操作,则打印结果为其他线程。
但是BlockOperation还提供了一个方法addExecutionBlock
,通过addExecutionBlock
就可以为NSBlockOperation添加额外的操作。这些操作(包括初始化Block中的操作)可以在不同的线程中同时(并发)执行。只有当所有相关的操作已经完成执行时,才视为完成。
如果添加的操作多的话,init(block: @escaping @Sendable () -> Void)
中的操作也可能会在其他线程(非当前线程)中执行,这是由系统决定的,并不是说添加到 init(block: @escaping @Sendable () -> Void)
中的操作一定会在当前线程中执行。
/**
使用子类 NSBlockOperation
调用方法 AddExecutionBlock
*/
private func useBlockOperation() {
// 1.创建 NSBlockOperation 对象
let op = BlockOperation.init {
for _ in 0..<2 {
print("3---\(Thread.current)")// 打印当前线程
}
}
// 2.添加额外的操作
op.addExecutionBlock {
for _ in 0..<2 {
print("4---\(Thread.current)")// 打印当前线程
}
}
// 2.调用 start 方法开始执行操作
op.start()
}
- 可以看出:使用子类
BlockOperation
,并调用方法 AddExecutionBlock: 的情况下,init(block: @escaping @Sendable () -> Void)
方法中的操作 和addExecutionBlock
中的操作是在不同的线程中异步执行的。而且,这次执行结果中init(block: @escaping @Sendable () -> Void)
方法中的操作也不是在当前线程(主线程)中执行的。从而印证了init(block: @escaping @Sendable () -> Void)
中的操作也可能会在其他线程(非当前线程)中执行。
3.自定义继承自NSOperation的子类
class GXCustomOperation: Operation {
override func main() {
if self.isCancelled == false {
print(Thread.current)
}
}
}
六、OperationQueue队列
1. 创建队列
OperationQueue 一共有两种队列:主队列、自定义队列。其中自定义队列同时包含了串行、并发功能。
- 主队列
凡是添加到主队列中的操作,都会放到主线程中执行(注:不包括操作使用addExecutionBlock:添加的额外操作,额外操作可能在其他线程执行)。
// 主队列获取方法
let queue = OperationQueue.main
- 自定义队列
添加到这种队列中的操作,就会自动放到子线程中执行。
同时包含了:串行、并发功能。
// 自定义队列创建方法
let queue1 = OperationQueue()
2. 将操作加入到队列中
- - (void)addOperation:(NSOperation *)op;
需要先创建操作,再讲创建好的操作加入到创建好的队列中去。
/** 使用addOperation: */
private func addOperationToQueue() {
// 1.创建队列
let queue = OperationQueue()
// 2.创建操作
// 使用 NSBlockOperation 创建操作3
let op1 = BlockOperation.init {
for _ in 0..<2 {
print("1---\(Thread.current)")// 打印当前线程
}
}
op1.addExecutionBlock {
for _ in 0..<2 {
print("2---\(Thread.current)")// 打印当前线程
}
}
let op2 = BlockOperation.init {
for _ in 0..<2 {
print("3---\(Thread.current)")// 打印当前线程
}
}
// 3.使用 addOperation: 添加所有操作到队列中
queue.addOperation(op1)
queue.addOperation(op2)
}
- addOperation(_ block: @escaping @Sendable () -> Void)
无需先创建操作,在block中添加操作,直接将包含操作的block加入到队列中。
/** 使用addOperationWithBlock: */
private func addOperationWithBlockToQueue() {
// 1.创建队列
let queue = OperationQueue()
// 2.使用 addOperationWithBlock: 添加操作到队列中
queue.addOperation {
for _ in 0..<2 {
print("1---\(Thread.current)")// 打印当前线程
}
}
queue.addOperation {
for _ in 0..<2 {
print("2---\(Thread.current)")// 打印当前线程
}
}
queue.addOperation {
print("3---\(Thread.current)")// 打印当前线程
}
}
七、OperationQueue 控制串行执行、并发执行
最大并发操作数:maxConcurrentOperationCount
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
串行队列
maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
private func serialQueue() {
print("start")
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1; // 串行队列
queue.addOperation {
Thread.sleep(forTimeInterval: 0.2)
print("0---\(Thread.current)")
}
queue.addOperation {
Thread.sleep(forTimeInterval: 0.2)
print("1---\(Thread.current)")
}
let operation1 = BlockOperation.init {
Thread.sleep(forTimeInterval: 0.2)
print("2---\(Thread.current)")
}
queue.addOperation(operation1)
print("end")
}
结果:
start
end
0---<NSThread: 0x600001747480>{number = 8, name = (null)}
1---<NSThread: 0x600001747480>{number = 8, name = (null)}
2---<NSThread: 0x600001747480>{number = 8, name = (null)}
并行队列
maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
结果:
start
end
1---<NSThread: 0x600001751a00>{number = 7, name = (null)}
0---<NSThread: 0x600001703a00>{number = 9, name = (null)}
2---<NSThread: 0x600001703e40>{number = 10, name = (null)}
八、Operation 操作依赖
func addDependency(_ op: Operation)
添加依赖,使当前操作依赖于操作 op 的完成。func removeDependency(_ op: Operation)
移除依赖,取消当前操作对操作 op 的依赖。var dependencies: [Operation] { get }
在当前操作开始执行之前完成执行的所有操作对象数组。
// 测试op依赖关系
// A,B - C
// C,D - E
func testDependency() {
let opA = BlockOperation()
let opB = BlockOperation()
let opC = BlockOperation()
let opD = BlockOperation()
let opE = BlockOperation()
/// 创建任务
opA.addExecutionBlock {
Thread.sleep(forTimeInterval: 0.2)
print("A--\(Thread.current)")
}
opB.addExecutionBlock {
Thread.sleep(forTimeInterval: 0.2)
print("B--\(Thread.current)")
}
opC.addExecutionBlock {
Thread.sleep(forTimeInterval: 0.2)
print("C--\(Thread.current)")
}
opD.addExecutionBlock {
Thread.sleep(forTimeInterval: 0.2)
print("D--\(Thread.current)")
}
opE.addExecutionBlock {
Thread.sleep(forTimeInterval: 0.2)
print("E--\(Thread.current)")
}
/// 添加依赖
opC.addDependency(opA)
opC.addDependency(opB)
opE.addDependency(opC)
opE.addDependency(opD)
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 6
queue.addOperations([opA, opB, opC, opD, opE], waitUntilFinished: false)
}
结果:
A--<NSThread: 0x60000174c200>{number = 9, name = (null)}
D--<NSThread: 0x600001758500>{number = 10, name = (null)}
B--<NSThread: 0x600001773280>{number = 8, name = (null)}
C--<NSThread: 0x600001773280>{number = 8, name = (null)}
E--<NSThread: 0x600001773280>{number = 8, name = (null)}
注意: 小心死锁, 如果Operation之间互相依赖,比如队列A中的Operation1依赖Operation2, 而Operation2依赖Operation3,Operation3依赖Operation1, 这就会陷入互相等待的死锁。
九、Operation 优先级
Operation 提供了queuePriority(优先级)属性,queuePriority属性适用于同一操作队列中的操作,不适用于不同操作队列中的操作。默认情况下,所有新创建的操作对象优先级都是normal。但是我们可以通过setQueuePriority:方法来改变当前操作在同一队列中的执行优先级。
// 优先级的取值
public enum QueuePriority : Int {
case veryLow = -8
case low = -4
case normal = 0
case high = 4
case veryHigh = 8
}
对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
那么,什么样的操作才是进入就绪状态的操作呢?
当一个操作的所有依赖都已经完成时,操作对象通常会进入准备就绪状态,等待执行。
举个例子:
现在有4个优先级都是 normal(默认级别)的操作:op1,op2,op3,op4。其中 op3 依赖于 op2,op2 依赖于 op1,即 op3 -> op2 -> op1。现在将这4个操作添加到队列中并发执行。
因为 op1 和 op4 都没有需要依赖的操作,所以在 op1,op4 执行之前,就是处于准备就绪状态的操作。
而 op3 和 op2 都有依赖的操作(op3 依赖于 op2,op2 依赖于 op1),所以 op3 和 op2 都不是准备就绪状态下的操作。理解了进入就绪状态的操作,那么我们就理解了queuePriority 属性的作用对象。
queuePriority 属性决定了进入准备就绪状态下的操作之间的开始执行顺序。并且,优先级不能取代依赖关系。
如果一个队列中既包含高优先级操作,又包含低优先级操作,并且两个操作都已经准备就绪,那么队列先执行高优先级操作。比如上例中,如果 op1 和 op4 是不同优先级的操作,那么就会先执行优先级高的操作。
如果,一个队列中既包含了准备就绪状态的操作,又包含了未准备就绪的操作,未准备就绪的操作优先级比准备就绪的操作优先级高。那么,虽然准备就绪的操作优先级低,也会优先执行。优先级不能取代依赖关系。如果要控制操作间的启动顺序,则必须使用依赖关系。
func testQueuePriority(){
let opA = BlockOperation()
let opB = BlockOperation()
let opC = BlockOperation()
opA.addExecutionBlock {
print("1--\(Thread.current)")
}
opB.addExecutionBlock {
print("2--\(Thread.current)")
}
opC.addExecutionBlock {
print("3--\(Thread.current)")
}
opA.queuePriority = .low
opB.queuePriority = .high
opC.queuePriority = .normal
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.addOperations([opA, opB, opC], waitUntilFinished: false)
}
十、OperationQueue 线程间的通信
1. 回到主线程
/// 回到主线程
func communication() {
OperationQueue().addOperation {
Thread.sleep(forTimeInterval: 0.2)
print("1---\(Thread.current)")
OperationQueue.main.addOperation({
Thread.sleep(forTimeInterval: 0.2)
print("2---\(Thread.current)")
})
}
}
2. 线程同步和线程安全
-
线程同步:
可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。 -
线程安全:
如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 -
线程安全解决方案:
可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。
iOS 实现线程加锁有很多种方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/ge等等各种方式。
代码:
var ticketSurplusCount = 50
private func saleTicketAction() {
/// 1.1 创建代表火车票售卖窗口1
let operationQueue1 = OperationQueue()
operationQueue1.maxConcurrentOperationCount = 1;
/// 1.2 创建卖票操作 op1
let op1 = BlockOperation{
self.saleTicketSafe()
}
/// 1.3 添加操作
operationQueue1.addOperation(op1)
/// 2.1创建代表火车票售卖窗口2
let operationQueue2 = OperationQueue()
operationQueue2.maxConcurrentOperationCount = 1;
/// 2.2创建卖票操作 op2
let op2 = BlockOperation{
self.saleTicketSafe()
}
/// 2.3 添加操作
operationQueue2.addOperation(op2)
}
private func saleTicketSafe(){
while true {
objc_sync_enter(self)
if self.ticketSurplusCount > 0 {
self.ticketSurplusCount-=1;
print("剩余票数:\(self.ticketSurplusCount) 窗口:\(Thread.current)")
Thread.sleep(forTimeInterval: 0.2)
}
objc_sync_exit(self)
if self.ticketSurplusCount <= 0 {
print("所有火车票均已售完")
break
}
}
}