iOS多线程--- Operation

一、Operation简介

Operation是苹果提供给我们的一套多线程解决方案。实际上Operation是基于GCD更高一层的封装,完全面向对象。但是比GCD更简单、代码可读性也更高。

二、Operation操作、OperationQueue操作队列

  • 操作(Operation):
    执行操作,在线程中执行的那段代码。
    在GCD中是放在block中的。在NSOperation中,我们使用NSOperation子类NSInvocationOperationBlockOperation,或者自定义子类来封装操作
  • 操作队列(Operation Queues)
    不同于GCD中的调度队列FIFO(先进先出)的原则。OperationQueue对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(费结算执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
    操作队列通过设置最大并发操作数(maxConcurrentOperationCount)来控制并发、串行。
    OperationQueue为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程上,而自定义队列在后台执行。

三、Operation、OPerationQueue使用步骤

Operation需要配合OperationQueue来实现多线程。因为默认情况下,Operation单独使用时系统同步执行操作,配合OperationQueue我们能更好的实现异步执行。
Operation实现多线程的使用步骤分为三步:

  1. 创建操作:先将需要执行的操作封装到一个Operation对象中。
  2. 创建队列:创建OperationQueue对象。
  3. 将操作加入到队列中:将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. 将操作加入到队列中

  1. - (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)
    }
  1. 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. 线程同步和线程安全

  1. 线程同步
    可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
    若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作(更改变量),一般都需要考虑线程同步,否则的话就可能影响线程安全。

  2. 线程安全
    如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

  3. 线程安全解决方案
    可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。
    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
            }
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值