iOS多线程之NSOperation、NSOperationQueue

通过一道小问题引出今天的内容

我们有A、B、C、D四个异步任务,AB执行结束才能执行C,A执行完成才能执行D

使用GCD解决

分两步实现

  • AB执行结束才能执行C,这个比较好实现,使用dispatch_group_t+dispatch_barrier就可以很容易实现
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t conQueue = dispatch_queue_create("conQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_group_async(group, conQueue, ^{
        sleep(6);
        NSLog(@"taskA");
        dispatch_semaphore_signal(semaphore);
    });

    dispatch_group_async(group, conQueue, ^{
        NSLog(@"taskB");
    });

    dispatch_barrier_async(conQueue, ^{
        NSLog(@"taskC");
    });
复制代码

注意要自己创建并发队列不要使用globalQueue,这里有坑点

  • A执行完成才能执行D

这一步似乎就没那么好实现,我们需要引入状态变量或者锁才能解决A执行完成才能执行D的问题,我们可以使用GCD的信号量来解决

    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t conQueue = dispatch_queue_create("conQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    dispatch_group_async(group, conQueue, ^{
        sleep(6);
        NSLog(@"taskA");
        dispatch_semaphore_signal(semaphore);
    });

    dispatch_group_async(group, conQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"taskD");
    });

    dispatch_group_async(group, conQueue, ^{
        NSLog(@"taskB");
    });

    dispatch_barrier_async(conQueue, ^{
        NSLog(@"taskC");
    });
复制代码

使用NSOperation、NSOperationQueue来解决

    NSBlockOperation *taskA = [NSBlockOperation blockOperationWithBlock:^{
        sleep(6);
        NSLog(@"taskA");
    }];

    NSBlockOperation *taskB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"taskB");
    }];

    NSBlockOperation *taskC = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"taskC");
    }];

    NSBlockOperation *taskD = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"taskD");
    }];

    [taskC addDependency:taskA];
    [taskC addDependency:taskB];
    [taskD addDependency:taskA];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:taskA];
    [queue addOperation:taskB];
    [queue addOperation:taskC];
    [queue addOperation:taskD];
复制代码

只需要添加这几行任务依赖关系就可以了

    [taskC addDependency:taskA];
    [taskC addDependency:taskB];
    [taskD addDependency:taskA];
复制代码

这样看来NSOperation、NSOperationQueue比GCD更加的面向对象,而不需要通过信号量这种过程变量来控制。

GCD已经能满足我们解决线程问题的需要,NSOperation、NSOperationQueue是基于GCD的一层封装,更加面向对象,使用起来更加简单,在解决诸如任务的依赖时尤为简单。

参考文章
iOS 多线程:『NSOperation、NSOperationQueue』详尽总结

NSOperation、NSOperationQueue 简介

NSOperation、NSOperationQueue是基于CGD的封装,那么NSOperation、NSOperationQueue同样有任务队列的概念

  • 操作(Operation):

    • 执行操作的意思,换句话说就是你在线程中执行的那段代码。
    • 在 GCD 中是放在 block 中的。在 NSOperation 中,我们使用 NSOperation 子类 NSInvocationOperationNSBlockOperation,或者自定义子类来封装操作。
  • 操作队列(Operation Queues):

    • 这里的队列指操作队列,即用来存放操作的队列。不同于 GCD 中的调度队列 FIFO(先进先出)的原则。NSOperationQueue 对于添加到队列中的操作,首先进入准备就绪的状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作的开始执行顺序(非结束执行顺序)由操作之间相对的优先级决定(优先级是操作对象自身的属性)。
    • 操作队列通过设置最大并发操作数(maxConcurrentOperationCount) 来控制并发、串行。
    • NSOperationQueue 为我们提供了两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。

NSInvocationOperation

    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(fun) object:nil];
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(fun1) object:nil];

    [operation start];
    [operation1 start];
复制代码

可以看到NSInvocationOperation是在当前线程同步执行,需要手动调起start方法

NSBlockOperation

    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        sleep(3);
        NSLog(@"fun");
    }];

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1");
    }];

    [operation start];
    [operation1 start];
复制代码

NSInvocationOperation类似,也是在当前线程同步执行,也需要手动调用start方法,另外还可以通过“addExecutionBlock`添加任务

    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock1-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock2-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock3-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock4-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock5-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock6-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock7-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock8-%@",[NSThread currentThread]);
    }];

    [operation start];
复制代码

image.png

通过打印结果可以看出通过这两种方式添加的任务是异步执行的,他们都可能在子线程中执行,注意:通过blockOperationWithBlock设置的任务也不一定在主线程(当前线程)中执行

NSOperationQueue

NSOperationQueue 一共有两种队列:主队列、自定义队列。其中自定义队列同时包含了串行、并发功能。主队列一定在主线程执行,执行顺序为串行同步执行

  • 主队列
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun-%@",[NSThread currentThread]);
    }];

    [[NSOperationQueue mainQueue] addOperation:operation];
复制代码

一定在主线程执行

  • 自定义队列
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun2-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun3-%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun4-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun5-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation6 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun6-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation7 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun7-%@",[NSThread currentThread]);
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
    [queue addOperation:operation5];
    [queue addOperation:operation6];
    [queue addOperation:operation7];
复制代码

image.png

可以看到自定义队列的任务一定在子线程并发执行,具有开启新线程的能力

maxConcurrentOperationCount

通过maxConcurrentOperationCount可以控制队列的最大并发操作数量,默认值为-1,表示不进行限制,最大开启并发数由系统控制,如果设置为1那就是串行执行,如果设置为大于1那么就取 min{自定义值,系统设定最大值},即使我们设置了一个很大的值,系统也不会无限制的开启如此多的并发操作.

  • 同步执行
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun2-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun3-%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun4-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation5 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"fun5-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation6 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun6-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation7 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun7-%@",[NSThread currentThread]);
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
    [queue addOperation:operation5];
    [queue addOperation:operation6];
    [queue addOperation:operation7];
复制代码

image.png

NSOperation 操作依赖

前面问题中我们就用了操作依赖addDependency,例如operation3依赖operation1operation2

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"fun2-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun3-%@",[NSThread currentThread]);
    }];

    [operation3 addDependency:operation1];
    [operation3 addDependency:operation2];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
复制代码

image.png

operation1operation2执行结束才会执行operation3

NSOperation 优先级queuePriority

优先级有五种

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};
复制代码

例如上面的例子我们将operation1优先级设置为veryLow,将operation3优先级设置为veryHigh,那么是不是operation3会比operation1更早执行呢????

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"fun2-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun3-%@",[NSThread currentThread]);
    }];

    [operation3 addDependency:operation1];
    [operation3 addDependency:operation2];

    [operation1 setQueuePriority:NSOperationQueuePriorityVeryLow];
    [operation3 setQueuePriority:NSOperationQueuePriorityVeryHigh];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
复制代码

image.png

可以看到仍然按照依赖关系先执行,优先级只是控制就绪状态的任务的开始执行顺序,注意是开始执行顺序,而不是结束执行顺序,先开始的任务不一定先结束,那么什么是就绪状态呢??

例如操作C依赖操作A和操作B,那么当AB直接结束之前C是处于非就绪状态的,操作A和操作B可以同时处于就绪状态,这个时候如果A的优先级高于B那么A就比B先开始执行,但是不一定比B早结束执行

线程间通讯

例如,网络耗时操作在子线程执行,结束之后在主线程刷新页面

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(3);//模拟耗时操作
        NSLog(@"fun1-%@",[NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //刷新页面
            NSLog(@"fun2-%@",[NSThread currentThread]);
        }];
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
复制代码

image.png

线程安全

可以加索保证线程安全

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        while (1) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            if (num>0) {
                num--;
                NSLog(@"%d-%@",num,[NSThread currentThread]);
            }
            dispatch_semaphore_signal(semaphore);
            if (num<=0) {
                break;
            }
        }
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        while (1) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            if (num>0) {
                num--;
                NSLog(@"%d-%@",num,[NSThread currentThread]);
            }

            dispatch_semaphore_signal(semaphore);
            if (num<=0) {
                break;
            }
        }
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
复制代码

NSOperation 常用属性和方法

  1. 取消操作方法
  • - (void)cancel; 可取消操作,实质是标记 isCancelled 状态。
  1. 判断操作状态方法
  • - (BOOL)isFinished; 判断操作是否已经结束。
  • - (BOOL)isCancelled; 判断操作是否已经标记为取消。
  • - (BOOL)isExecuting; 判断操作是否正在在运行。
  • - (BOOL)isReady; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
  1. 操作同步
  • - (void)waitUntilFinished; 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。
  • - (void)setCompletionBlock:(void (^)(void))block; completionBlock 会在当前操作执行完毕时执行 completionBlock。
  • - (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。
  • - (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
  • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

# NSOperationQueue 常用属性和方法

  • 取消/暂停/恢复操作

    • - (void)cancelAllOperations; 可以取消队列的所有操作。
    • - (BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。
    • - (void)setSuspended:(BOOL)b; 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。
  • 操作同步

    • - (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕。
  • 添加/获取操作`

    • - (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象。
    • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
    • - (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。
    • - (NSUInteger)operationCount; 当前队列中的操作数。
  • 获取队列

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

注意:

  1. 这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
  2. 暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

参考文章

iOS 多线程:『NSOperation、NSOperationQueue』详尽总结

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享