美文网首页
多线程-Operation

多线程-Operation

作者: 你weixiao的时候很美 | 来源:发表于2018-03-27 17:53 被阅读9次

多线程之Operation

本文参考 reywenderlich 的一篇文章和demo介绍自定义operation 文章地址
本文参考简书上的一篇总结文章 文章地址

1. Operation 和 OperationQueue 介绍

  • operation和operationQueue是苹果提供的一套多线程的解决方案。
  • 基于GCD的更高一层的封装。
  • 相比于GCD,它可以设置操作的优先级,依赖关系。
  • 相比于GCD,它可以取消,挂起,恢复。

2. 基本概念

  • Operation,操作,就是在线程中执行的那段代码。 在gcd中放在block中。 在Operation中放在 NSInvocationOperation,NSBlockOperation,或者自定义的子类来封装操作。
  • OperationQueue,操作队列,存放操作的队列。有OperationQueue为我们提供了两个不同类型的队列: 主队列 和自定义队列。 主队列在主线程运行,自定义队列在子线程运行。

3. NSInvocationOperation使用方法

  • invocationOperation添加操作的方法是通过 target-action方式。
  • 如果没有将Operation添加到OperationQueue中,操作是在当前线程完成的,不会开启新线程。
  • 如果将invocationOperation添加到自定义queue中,操作会在子线程完成。
-(void)testInvocationOperation{
    NSInvocationOperation* operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationTask) object:nil];
//    [operation  start];              //不添加到queue中,直接调用start,会直接在当前线程完成操作。
    
    NSOperationQueue* queue =[[NSOperationQueue  alloc]init];
    [queue addOperation:operation];       // 添加到自定义queue中会在子线程完成操作。
}
-(void)invocationTask{
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"%@",[NSThread currentThread]);
}

4.BlockOperation用法

  • blockOperation的第一个api是 blockOperationWithBlock 如果没有使用 NSOperationQueue、在主线程中单独使用 NSBlockOperation 执行一个操作的情况下,操作是在当前线程执行的,并没有开启新线程。
-(void)testBlockOperation{
    NSBlockOperation* blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2.0];
        NSLog(@"%@",[NSThread currentThread]);  //打印当前下线程
    }];
    
    [blockOperation start];
}

和上边 NSInvocationOperation 使用一样,在operation不添加到queue的情况下,代码是在主线程中调用的,所以打印结果为主线程。如果在其他线程中执行操作,则打印结果为其他线程。

  • blockOperation的第二个api是 addExecutionBlock 可以为 NSBlockOperation 添加额外的操作。这些操作(包括 blockOperationWithBlock 中的操作)可以在不同的线程中同时(并发)执行。只有当所有相关的操作已经完成执行时,才视为完成
-(void)testBlockOperationAddExecutionBlockMethod{
    // 1.创建 NSBlockOperation 对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }];
    
    op.completionBlock = ^{
        NSLog(@"完成----%@",[NSThread currentThread]);
    };
    
    // 2.添加额外的操作
    [op addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
    }];
    [op addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3---%@", [NSThread currentThread]);
    }];
    [op addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"4---%@", [NSThread currentThread]);
    }];
    
    [op start];
}
屏幕快照 2018-03-27 上午11.28.59.png

添加的操作多的话,blockOperationWithBlock: 中的操作也可能会在其他线程(非当前线程)中执行,这是由系统决定的,并不是说添加到 blockOperationWithBlock: 中的操作一定会在当前线程中执行

5. 自定义Operation的使用

  • 使用自定义的继承自Operation的子类, 可以重写main方法来自定义自己的Operation对象。当mian方法执行完成返回的时候,这个操作就结束了。
  • 没有使用 NSOperationQueue的时候、在主线程单独使用自定义继承自 NSOperation 的子类的情况下,是在主线程执行操作,并没有开启新线程。
@implementation CustomOperation  // 继承自NSOperation

-(void)main{
    if (self.isCancelled) {
        return;
    }
    [NSThread sleepForTimeInterval:2.0];
    NSLog(@"%@",[NSThread currentThread]);
}
-(void)testCustomOperation{      // 直接start 或者加入到queue中
    CustomOperation* customOperation = [[CustomOperation alloc]init];
    [customOperation start];
}

6. OperationQueue 的使用

6.1 创建队列
  • OperationQueue一共有两种队列, 主队列和自定义队列。
  • 主队列:添加到主队列中的操作,都会在主线程执行。
// 主队列获取
    NSOperationQueue * mainQueue = [NSOperationQueue mainQueue];
  • 自定义队列: 添加到自定义队列中的操作,会指定放在子线程中执行。 同时包含了 串行,并行的功能。
// 自定义队列的创建
    NSOperationQueue* customQueue = [[NSOperationQueue alloc]init];
6.2 将Operation添加到自定义的队列,开启多线程,进行并发执行。
    1. 通过addOperation: 方法
-(void)testQueueAddOperation{
    
    NSInvocationOperation* invocationOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(invocationTask) object:nil];
    
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"blockOperation---%@", [NSThread currentThread]); // 打印当前线程
    }];
    
    [self.customQueue addOperation:invocationOperation];
    [self.customQueue addOperation:blockOperation];
}
    1. 通过addOperationWithBlock:(void (^)(void))block方法, 直接添加了一个操作,无需创建一个Operation
 //直接通过block添加Operation。
    [self.customQueue addOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"block---%@", [NSThread currentThread]); // 打印当前线程
    }];
6.3 使用OperationQueue控制串行和并行
  • 使用OperationQueue,将Operation添加到自定义的队列中, 可以在子线程并发执行操作。
  • 如果想要在子线程 串行执行操作。 可以设置一个属性 maxConcurrentOperationCount 最大并发数,来控制一个特定的队列中,可以有多少个操作并发执行。

这里的 maxConcurrentOperationCount 控制的并不是并发线程的数量, 而是一个队列中并发的操作的数量。

  • maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
    maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
    maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整

当最大并发操作数为1时,操作是按顺序串行执行的,并且一个操作完成之后,下一个操作才开始执行。当最大操作并发数为2时,操作是并发执行的,可以同时执行两个操作。而开启线程数量是由系统决定的,不需要我们来管理。

6.4 Operation操作的依赖

NSOperationQueue 最吸引人的地方是它能添加操作之间的依赖关系。通过操作依赖,我们可以很方便的控制操作之间的执行先后顺序, 有3个接口可以管理和查看依赖。

  • addDependency:(NSOperation *)op 添加依赖。
  • removeDependency:(NSOperation *)op 移除依赖
  • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。
-(void)testDependency{
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
    }];
    
    [op1 addDependency:op2];
    
    [self.customQueue addOperation:op1];
    [self.customQueue addOperation:op2];
}

无论运行几次都是op2先执行

6.5 Operation操作的优先级

Operation提供 queuePriority属性。 适用于同一个队列中的操作。默认优先级是normal。

//系统定义的 enum
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
  • 把操作添加到队列中后, 当一个操作的依赖关系完成后,操作对象进入 准备就绪 状态,等待执行。
  • 进入准备就绪状态后,操作的 开始执行顺序 由操作之间相对的优先级决定。
  • 优先级不能取代依赖关系,如果要控制操作的执行顺序,必须使用依赖关系。
  • 举个例子,现在有4个优先级都是 NSOperationQueuePriorityNormal(默认级别)的操作:op1,op2,op3,op4。其中 op3 依赖于 op2,op2 依赖于 op1,即 op3 -> op2 -> op1。现在将这4个操作添加到队列中并发执行。
    因为 op1 和 op4 都没有需要依赖的操作,所以在 op1,op4 执行之前,就是处于准备就绪状态的操作。
    而 op3 和 op2 都有依赖的操作(op3 依赖于 op2,op2 依赖于 op1),所以 op3 和 op2 都不是准备就绪状态下的操作。
6.6 NSOperationQueue 线程间的通信

operationQueue 使用 [NSOperationQueue mainQueue]addOperationWithBlock: 方法来使子线程和主线程通讯。

//1.
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        //1.1 耗时操作
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        // 1.2回到主线程
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2]; 
            NSLog(@"main---%@", [NSThread currentThread]); // 打印当前线程
        }];
    }];
    //2.
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        //2.2
        [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
        NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
    }];
    //3.
    [op1 addDependency:op2];
    //4.
    op1.completionBlock = ^{
        //4.1
       NSLog(@"op1完成---%@", [NSThread currentThread]); // 打印当前线程
    };
    //5.
    [self.customQueue addOperation:op1];
    [self.customQueue addOperation:op2];
}
屏幕快照 2018-03-27 下午4.15.10.png

假如上边的一段代码是在主线程下执行的。 那么按照时间的执行顺序是
主线程先执行1. 创建一个带block的op1 -> 主线程执行2. 创建一个带block的op2 -> 主线程执行第三句话3. 添加了依赖 -> 主线程执行第四局4. 给op1 设置了一个block的属性 -> 主线程执行第五句话5. 将op1和op2添加到queue中。 此时某个子线程(这里是子线程3)开始执行2.2 的Op2的block中的操作,睡了2秒,打印 -> 完成以后某个子线程(这里还是子线程3)开始执行op1中的操作1.2,睡了两秒,然后打印。然后执行的操作是 给主队列添加了一个 操作的block,然后op1完成了 -> 然后某个子线程执行完成op1的操作4.1 与此同时,因为主队列因为加入了操作1.2 里的操作。主线程此时执行1.2里的操作。入下图所示

屏幕快照 2018-03-27 下午5.06.28.png

7. 线程安全

  • 在一个进程中,多个线程同时运行,如果同时 对一块内存 进行写操作,可能会影响线程安全。
  • 可以对操作进行加锁
-(void)testSafe{
    self.ticketSurplusCount = 50;
    // 1.创建 queue1
    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    queue1.maxConcurrentOperationCount = 1;
    // 2.创建 queue2
    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
    queue2.maxConcurrentOperationCount = 1;
    
    // 3.创建操作op1
    __weak typeof(self) weakSelf = self;
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketNotSafe];
//        [weakSelf saleTicketSafe];     //加lock,线程安全
    }];
    // 4.创建操作op2
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [weakSelf saleTicketNotSafe];
//        [weakSelf saleTicketSafe];     //加lock,线程安全
     }];
    // 5.添加操作
    [queue1 addOperation:op1];
    [queue2 addOperation:op2];
}

- (void)saleTicketNotSafe {
    while (1) {
        
        if (self.ticketSurplusCount > 0) {
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余数:%ld 线程:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else {
            NSLog(@"所有数已经减完");
            break;
        }
    }
}

- (void)saleTicketSafe {
    while (1) {
        
        // 加锁
        [self.lock lock];
        if (self.ticketSurplusCount > 0) {
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余数:%ld 线程:%@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }
        // 解锁
        [self.lock unlock];
        if (self.ticketSurplusCount <= 0) {
            NSLog(@"所有数已经减完");
            break;
        }
    }
}

8. 常见属性和api

8.1 Operation的属性和方法

// 取消操作
-(void)cancel 取消操作,实质是 标记Operation的isCancelled

//判断状态

  • isFinished,isCancelled,isExecuting,isReady,判断Operation 是否已结束,已经取消,在运行,准备就绪。

// 操作
-(void)setCompletionBlock:(void (^)(void))block; completionBlock 会在当前操作执行完毕时执行 completionBlock。

(void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成

(void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖

@property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组

8.2 OperationQueue的常见属性和操作

// 取消,暂停,恢复队列
(void)cancelAllOperations; 可以取消队列的所有操作。

(BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。

(void)setSuspended:(BOOL)b; 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列

// 操作和获取Operation
(void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象。

(NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。

(NSUInteger)operationCount; 当前队列中的操作数

// 获取队列

  • (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
  • (id)mainQueue; 获取主队列

相关文章

  • Swift 5.x 多线程

    Swift多线程编程方案: Thread Cocoa Operation (Operation 和 Operati...

  • iOS多线程操作:NSOperation

    Operation也是我们常见的多线程操作方式之一,在许多知名框架的多线程操作都是使用的Operation这种操作...

  • 多线程-Operation

    多线程之Operation 本文参考 reywenderlich 的一篇文章和demo介绍自定义operation...

  • IOS多线程

    多线程技术 Thread 给线程加锁使线程同步 Operation

  • 多线程Operation

    NSOperation的作用配合使用NSOperation和NSOperationQueue也能实现多线程编程NS...

  • iOS-Swift相关

    swift程序. 1.Swift多线程之Operation:异步加载CollectionView图片2.Swift...

  • 多线程之operation

    1.NSOperation简介NSOperation是苹果公司对于GCD的封装,比GCD更简单易用、代码可读性也更...

  • 多线程 NSThread - Operation

    1.NSThread 每个NSThread对象对应一个线程,真正最原始的线程。 优点:NSThread轻量级最低,...

  • 多线程之Operation

    前言:Operation的底层就是对GCD的二次封装,所以思路和用法基本一致 自定义operation的话要继承O...

  • 多线程之 Operation Queue 与 GCD

    前言 在iOS 开发中, 实现多线程方案有:Operation Queue 、GCD、NSTread 。而大家经常...

网友评论

      本文标题:多线程-Operation

      本文链接:https://www.haomeiwen.com/subject/librcftx.html