简介
NSOperation
、NSOperationQueue
是苹果提供的一套多线程解决方案,是基于GCD
更高一层的封装,完全面向对象。但比GCD
更简单易用、代码可读性更高。
- 好处:
可添加完成的代码块complete
,在操作完成后执行。
添加操作之间的依赖关系,方便控制执行顺序。
设定操作执行的优先级。
可以取消一个操作的执行。
使用 KVO 观察执行状态:isExecuteing、isFinished、isCancelled
。
在 NSOperation、NSOperationQueue
中也有类似的“任务”和“队列”的概念。
1.NSOperation
任务,或操作。即,你在线程中执行的那段代码。NSOperation
是一个抽象类,要使用其子类 NSInvocationOperation、NSBlockOperation
,或者自定义子类来封装任务。
2.NSOperationQueue
指用来存放操作的队列。
-
队列类型
主队列、自定义队列。
主队列运行在主线程之上,而自定义队列在后台执行。 -
任务状态和顺序
不同于 GCD 中的队列 FIFO(先进先出)的原则。对于添加到NSOperationQueue
中的任务,首先进入准备就绪的状态(就绪状态取决于任务之间的依赖关系),然后进入就绪状态的任务的开始执行顺序(而非执行完成的顺序)由任务之间相对的优先级决定。 -
并发和串行
任务队列通过设置最大并发操作数maxConcurrentOperationCount
来控制并发、串行。
3. NSOperation实现多线程
默认情况下,NSOperation
单独使用时,系统同步执行操作。只有配合 NSOperationQueue
才能更好地实现异步执行。
- 使用步骤分为3步
创建任务:先将需要执行的任务,封装到一个NSOperation
对象中。
创建队列:即NSOperationQueue
对象。
将任务加入到队列中:将NSOperation
对象添加到NSOperationQueue
对象中。
系统就会自动将NSOperationQueue
中的NSOperation
取出来,在新线程中执行操作。
3.1创建NSOperation任务
NSOperation
是个抽象类,不能直接用来封装任务。要使用它的子类来封装任务:NSInvocationOperation、NSBlockOperation
以及自定义的子类。
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
[op start]; // 开始执行
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// do some thing...
}];
[op start];
此外,可以为任务对象添加额外任务。
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// do something...
}];
// 添加额外的操作
[op addExecutionBlock:^{
// do otherthing...
}];
- 在没有使用
NSOperationQueue
、在主线程中单独使用NSOperation
的子类对象 执行一个任务的情况下,任务是在当前线程执行的,并没有开启新线程。- 使用子类
NSBlockOperation
,并调用方法addExecutionBlock:
的情况下,如果添加的操作的个数多,就会自动开启新线程。
2.创建NSOperationQueue队列
有两种:主队列、自定义队列。
- 主队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
凡是添加到主队列中的操作,都会放到主线程中执行。
- 自定义队列
添加到这种队列中的操作,就会自动放到子线程中执行;同时包含串行、并发功能。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue. maxConcurrentOperationCount = 3;
可设置属性maxConcurrentOperationCount
最大并发操作数,决定串行、并发。
属性
maxConcurrentOperationCount
= 1; // 串行队列,任务只能按顺序串行执行的
= 2; // 并发队列,>2时,任务是并发执行的,可同时执行多个操作
= 8; // 并发队列
当然这个值不应超过系统限制,即使设置了一个很大的值,系统也会自动调整为 min。
3.将操作加入到队列中
方法addOperation:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
//.....
}];
[op3 addExecutionBlock:^{
//.....
}];
[queue addOperation:op1];
[queue addOperation:op3];
方法addOperationWithBlock:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 添加操作到队列中
[queue addOperationWithBlock:^{
//.....
}];
操作和操作队列、使用步骤和基本使用方法、控制串行/并发执行、
- 把任务添加到队列中,相当于调用了任务的start方法
NSOperation 依赖
通过依赖,可以很方便的控制任务之间的执行先后顺序。
addDependency:(NSOperation *)op;
添加依赖
removeDependency:(NSOperation *)op;
移除依赖
NSArray<NSOperation *> *dependencies;
在当前任务开始执行之前,已完成任务对象的数组
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
//.....
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
//.....
}];
// 添加依赖
[op2 addDependency:op1]; //让op2 依赖于 op1。表示先执行op1
[queue addOperation:op1];
[queue addOperation:op2];
- 准备就绪状态
当一个任务的所有依赖都已经完成时,任务对象通常会进入准备就绪状态,等待执行。
NSOperation 优先级
NSOperation
提供了优先级属性queuePriority
。默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal
。但可以通过setQueuePriority:
方法来改变在同一队列中的任务的优先级。
queuePriority
属性适用于同一队列中的任务,不同队列的任务不适用。
优先级的取值:
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
(默认)
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
优先级与依赖
- 优先级属性
queuePriority
决定了进入准备就绪状态下的任务之间的开始执行顺序
。并且,优先级不能取代依赖关系。 - 如果一个队列中既包含高优先级任务,又包含低优先级任务,并且两个任务都已经准备就绪,那么队列先执行高优先级任务。
- 优先级不能取代依赖关系。
如果,一个队列中既包含了准备就绪状态的任务A,又包含了未准备就绪的任务B,B的优先级比A高。那么,会优先执行B。如果要控制操作间的启动顺序,则必须使用依赖关系。
线程间的通信
处理完子行程的任务,返回主行程处理
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperationWithBlock:^{
// .....
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 回到主线程, 进行一些 UI 刷新等操作
}];
}];
取消线程
正在执行的操作,无法取消。只能取消未执行的操作。
1.可通过 cancel 方法,取消未执行的单个操作。
NSOperationQueue *queue1 = [[NSOperationQueue alloc]init];
NSBlockOperation *block1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block11");
}];
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block22");
}];
NSBlockOperation *block3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"block33");
}];
[block3 cancel];
[queue1 addOperations:@[block1,block2,block3] waitUntilFinished:YES];
- cancelAllOperations方法,但无法移除 正在执行的操作。
// 移除队列里面所有的操作
[queue1 cancelAllOperations];
3.挂起队列,使队列任务不再执行,但正在执行的操作无法挂起。
queue1.suspended = YES;
要实现取消正在执行的操作,可以自定义NSOperation,其实就是拦截main方法。
main方法:
1.任何操作在执行时,首先会调用start方法,它会更新操作的状态过滤操作(如过滤掉处于“取消”状态的操作)
2.经start方法过滤后,只有正常可执行的操作,就会调用main方法。
3.重写操作的入口方法(main),就可以在这个方法里面指定操作执行的任务。
4.main方法默认是在子线程异步执行的。
线程同步和线程安全
-
线程安全
有多个线程可能会同时运行一段代码。如果每次运行结果和单线程运行的结果一样,且其他变量的值也和预期的是一样的,就是线程安全的。
若每个线程中对全局变量、静态变量只有读操作(无写操作),一般这个变量是线程安全的。
若有多个线程同时执行写操作(更改变量的值),就需要考虑线程同步,否则就可能影响线程安全。 -
线程同步
可理解为线程 A 和 线程 B 一块配合,A 执行到一定程度时要依靠线程 B 的某个结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A 再继续操作。
例子:火车票售卖
线程安全解决方案:加锁
可以给线程加锁,在一个线程执行该操作的时候,不允许其他线程进行操作。
iOS 实现线程加锁有很多种方式:
dispatch_semaphore
:建议使用,性能较好
NSLock
@synchronized
:性能最差
os_unfair_lock
:iOS10开始
NSCondition
NSConditionLock
pthread_mute
OSSpinLock
:iOS10 废弃
atomic(property) set/get
:原子操作
思想:执行写操作前,上锁,写操作完,解锁。
自旋锁:
会忙等。所谓忙等,即在访问被锁资源时,调用者线程不会休眠,而是不停循环在那里,直到被锁资源释放锁。
适用的情况
预计线程等待锁的时间很短
加锁的代码(临界区)经常被调用,但竞争情况很少发生
CPU资源不紧张、多核处理器
互斥锁:
会休眠。 所谓休眠,即在访问被锁资源时,调用者线程会休眠,此时cpu可以调度其他线程工作。直到被锁资源释放锁。此时会唤醒休眠线程。
适用的情况
预计线程等待锁的时间较长
临界区竞争非常激烈
临界区有IO操作
临界区代码复杂或者循环量大
单核处理器
死锁:
通常是指2个操作相互等待对方完成,造成死循环,于是2个操作都无法进行,就产生了死锁。
常用属性和方法归纳
.
NSOperation 和GCD的对比
-
NSOperation 是对线程的高度抽象,在项目中使 用它,会使项目的程序结构更好。子类化 NSOperation 的设计思路,是具有面向对 象的优点(复用、封装),使得实现是多线程支持,而接口简单。
建议在复杂项目中 使用。 -
GCD 本身非常简单、易用,对于不复杂的多线程操 作,会节省代码量,而 Block 参数的使用,会是代码更为易读。
建议在简单项目中 使用。 -
NSOperation的优点
1.NSOperationQueue可以轻松在Operation间设置依赖关系,而GCD 需要写很多代码才能实现。
- NSOperation可以很方便地调整执⾏顺序、设置最⼤并发数。
3.NSOperationQueue⽀支持KVO,可以监测operation的状态:是否正在执行(isExecuted)
、 是否结束(isFinished)
,是否取消(isCanceld)
4.GCD只⽀支持FIFO的队列列
网友评论