多线程之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添加到自定义的队列,开启多线程,进行并发执行。
- 通过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];
}
- 通过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
屏幕快照 2018-03-27 下午5.06.28.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里的操作。入下图所示
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; 获取主队列
网友评论