通过一道小问题引出今天的内容
我们有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 子类 NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。
-
操作队列(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];
复制代码
通过打印结果可以看出通过这两种方式添加的任务是异步执行的,他们都可能在子线程中执行,注意:通过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];
复制代码
可以看到自定义队列的任务一定在子线程并发执行,具有开启新线程的能力
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];
复制代码
NSOperation 操作依赖
前面问题中我们就用了操作依赖addDependency
,例如operation3
依赖operation1
和operation2
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];
复制代码
operation1
和operation2
执行结束才会执行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];
复制代码
可以看到仍然按照依赖关系先执行,优先级只是控制就绪状态的任务的开始执行顺序,注意是开始执行顺序,而不是结束执行顺序,先开始的任务不一定先结束,那么什么是就绪状态呢??
例如操作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];
复制代码
线程安全
可以加索保证线程安全
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 常用属性和方法
- 取消操作方法
- (void)cancel;
可取消操作,实质是标记 isCancelled 状态。
- 判断操作状态方法
- (BOOL)isFinished;
判断操作是否已经结束。- (BOOL)isCancelled;
判断操作是否已经标记为取消。- (BOOL)isExecuting;
判断操作是否正在在运行。- (BOOL)isReady;
判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
- 操作同步
- (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;
获取主队列。
注意:
- 这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
- 暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。