多线程-Operation

多线程之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; 获取主队列
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容

  • 原创文章转载请注明出处,谢谢 关于OC中的nil, NULL详解 我相信很多同学对于这四种表示空的方式肯定不会陌生...
    北辰明阅读 12,577评论 3 42
  • 文/一阵小旋风 大概每一个单身的人,心里都住着一个不可能的人,与其说是不可能,不如说舍不得忘记的人。 "老板,来一...
    北有晴空阅读 423评论 0 1
  • 从前,有一个小女孩,她好小好小,小小的个子,小小的力气,连每个五官都是小小的。她原本是不谙世事、不惹尘埃地过...
    胡岱阅读 575评论 1 1
  • 时光荏苒,当我们还沉浸在寒冬的凛冽时,早春已将一种极致的婉约,律动在岁月的双眸。凭栏听风,安于这此时此处的静...
    可可222阅读 498评论 4 1
  • 他们站得整整齐齐 笑得整整齐齐 整齐得像一个娘养的 我憋得面色发青 像卖刀的杨志 像后娘养的 杨志 撒泡尿的功夫 ...
    勒尤阅读 76评论 0 1