(由于合在一起感觉一篇太长翻着累)
iOS多线程目前总结了四篇
欢迎移步O(∩_∩)O
目录
- NSOperation
- 队列与操作
- 队列NSOperationQueue
- 操作NSOperation
- 阻塞
- NSOperation的API
NSOperation
NSOperation是苹果GCD、面向对象的封装。
相比GCD的优点:
队列与操作:
既然是GCD的封装、自然逃不掉GCD的基本概念。操作与队列。
-
队列NSOperationQueue
三种向队列添加操作的方式
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
- (void)addOperationWithBlock:(void (^)(void))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
队列插入操作后的执行顺序:
首先、无论串行并行(并发数是否为1)的条件下。队列内操作的执行依赖两个要素。
- 如果所插入的操作存在依赖关系、优先完成依赖操作。
-
如果所插入的操作不存在依赖关系、队列并发数为1下采用先进先出的原则、反之直接开辟新的线程执行
具体可以看下面的例子:
//创建操作队列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
//创建最后一个操作
NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"最后的任务");
}];
for (int i=0; i<5-1; ++i) {
//创建多线程操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
sleep(i);
NSLog(@"第%d个任务",i);
}];
//设置依赖操作为最后一个操作
[blockOperation addDependency:lastBlockOperation];
[operationQueue addOperation:blockOperation];
}
//将最后一个操作加入线程队列
[operationQueue addOperation:lastBlockOperation];
有依赖的情况下输出:
2018-03-16 15:08:21.666983+0800 test[4524:404134] 最后的任务
2018-03-16 15:08:21.667267+0800 test[4524:404135] 第0个任务
2018-03-16 15:08:22.667647+0800 test[4524:404141] 第1个任务
2018-03-16 15:08:23.672276+0800 test[4524:404134] 第2个任务
2018-03-16 15:08:24.669316+0800 test[4524:404132] 第3个任务
相对的、我们可以取消依赖:注释掉[blockOperation addDependency:lastBlockOperation];
2018-03-16 15:09:18.637169+0800 test[4551:406003] 第0个任务
2018-03-16 15:09:19.641270+0800 test[4551:406002] 第1个任务
2018-03-16 15:09:19.641270+0800 test[4551:406013] 最后的任务
2018-03-16 15:09:20.640994+0800 test[4551:406006] 第2个任务
2018-03-16 15:09:21.637335+0800 test[4551:406005] 第3个任务
我们也可以看看多线程的提现、另1号操作依赖addDependency:lastBlockOperation。看看其他操作会不会直接执行。
//设置依赖操作为最后一个操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
sleep(i);
NSLog(@"第%d个任务",i);
}];
//设置依赖操作为最后一个操作
if (i == 1) {
[blockOperation addDependency:lastBlockOperation];
}
[operationQueue addOperation:blockOperation];
结果确实是除了1号操作、其他的都立即被分配的其他线程执行了。
2018-03-16 15:20:16.679755+0800 test[4753:424790] 第0个任务
2018-03-16 15:20:18.682874+0800 test[4753:424792] 第2个任务
2018-03-16 15:20:18.682875+0800 test[4753:424795] 最后的任务
2018-03-16 15:20:19.683923+0800 test[4753:424793] 第3个任务
2018-03-16 15:20:19.683923+0800 test[4753:424790] 第1个任务
需要注意的是:
- 依赖必须在操作被添加到队列(确切来说应该是被执行)之前设置、否则无效。比如我们将一下两句调转:
[operationQueue addOperation:blockOperation];
[blockOperation addDependency:lastBlockOperation];
输出的结果和注释掉依赖后相同:
2018-03-16 15:10:48.814888+0800 test[4590:408731] 第0个任务
2018-03-16 15:10:49.817847+0800 test[4590:408732] 第1个任务
2018-03-16 15:10:49.817850+0800 test[4590:408731] 最后的任务
2018-03-16 15:10:50.815294+0800 test[4590:408733] 第2个任务
2018-03-16 15:10:51.815585+0800 test[4590:408730] 第3个任务
- 依赖在添加进队列之后虽然不能追加。但是可以对某操作进行追加
addExecutionBlock
、也可以延后操作的执行。
- (void)operationTest {
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
NSBlockOperation * blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"进入操作1");
sleep(3);
NSLog(@"操作1完成");
}];
NSBlockOperation * blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"进入依赖操作");
}];
[blockOperation2 addDependency:blockOperation1];
[operationQueue addOperation:blockOperation1];
[operationQueue addOperation:blockOperation2];
[blockOperation1 addExecutionBlock:^{
NSLog(@"进入追加操作");
sleep(5);
NSLog(@"追加操作完成");
}];
}
打印结果:
2018-03-20 13:00:55.635675+0800 test[2123:222591] 进入操作1
2018-03-20 13:00:55.635675+0800 test[2123:225319] 进入追加操作
2018-03-20 13:00:58.639154+0800 test[2123:222591] 操作1完成
2018-03-20 13:01:00.641240+0800 test[2123:225319] 追加操作完成
2018-03-20 13:01:00.641511+0800 test[2123:225319] 进入依赖操作
- 操作的依赖关系与本身绑定、并不受限于同一个队列。即使所执行的队列不同、也可以完成依赖操作。
NSOperationQueue *operationQueue1=[[NSOperationQueue alloc]init];
NSOperationQueue *operationQueue2=[[NSOperationQueue alloc]init];
//创建最后一个操作
NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"最后的任务");
}];
NSBlockOperation *blockOperation0=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"第0个任务");
}];
NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"第1个任务");
}];
[blockOperation1 addDependency:lastBlockOperation];
[operationQueue1 addOperation:blockOperation1];
[operationQueue1 addOperation:blockOperation0];
[operationQueue2 addOperation:lastBlockOperation];
打印结果:
2018-03-16 15:28:26.517760+0800 test[4874:439297] 第0个任务
2018-03-16 15:28:29.520710+0800 test[4874:439298] 最后的任务
2018-03-16 15:28:29.521107+0800 test[4874:439299] 第1个任务
操作的追加
我们可以通过一下方法将新的操作追加到NSBlockOperation对象中
- (void)addExecutionBlock:(void (^)(void))block;
但需要注意的是、追加的操作是并发执行的。具体的最大并发数、应该是由系统决定(因为我没找到哪个属性可以设置)。
//主队列、必然是并发为1
NSOperationQueue *operationQueue=[NSOperationQueue mainQueue];
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
sleep(1);
NSLog(@"第%d个任务--%@",10,[NSThread currentThread]);
}];
for (int i=0; i<10; ++i) {
[blockOperation addExecutionBlock:^{
sleep(1);
NSLog(@"第%d个任务--%@",i,[NSThread currentThread]);
}];
}
[operationQueue addOperation:blockOperation];
打印结果
2018-03-16 16:21:38.739533+0800 test[5491:504081] 第0个任务--<NSThread: 0x60400026ea80>{number = 3, name = (null)}
2018-03-16 16:21:38.739535+0800 test[5491:504020] 第10个任务--<NSThread: 0x60400006a000>{number = 1, name = main}
2018-03-16 16:21:38.739537+0800 test[5491:504083] 第1个任务--<NSThread: 0x60400026a3c0>{number = 4, name = (null)}
2018-03-16 16:21:38.739565+0800 test[5491:504080] 第2个任务--<NSThread: 0x60000026ba80>{number = 5, name = (null)}
2018-03-16 16:21:39.739936+0800 test[5491:504083] 第5个任务--<NSThread: 0x60400026a3c0>{number = 4, name = (null)}
2018-03-16 16:21:39.739936+0800 test[5491:504020] 第4个任务--<NSThread: 0x60400006a000>{number = 1, name = main}
2018-03-16 16:21:39.739936+0800 test[5491:504080] 第6个任务--<NSThread: 0x60000026ba80>{number = 5, name = (null)}
2018-03-16 16:21:39.739936+0800 test[5491:504081] 第3个任务--<NSThread: 0x60400026ea80>{number = 3, name = (null)}
2018-03-16 16:21:40.741238+0800 test[5491:504020] 第7个任务--<NSThread: 0x60400006a000>{number = 1, name = main}
2018-03-16 16:21:40.741239+0800 test[5491:504083] 第8个任务--<NSThread: 0x60400026a3c0>{number = 4, name = (null)}
2018-03-16 16:21:40.741290+0800 test[5491:504080] 第9个任务--<NSThread: 0x60000026ba80>{number = 5, name = (null)}
-
操作NSOperation
两种个子类NSBlockOperation
和NSInvocationOperation
、(当然、你也可以继承出一个NSOperation)。
操作的优先级
优先级只体现在两个时间点:
- 依赖任务处理完成、队列对后续任务的调度。
- 依赖队列从暂停转变为重新启动、后续任务的调度。
简而言之就是。在队列同时需要调度执行的任务中、会按照优先级排序执行。
如下所示(举个依赖完成的例子):
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount = 1;
NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"低优先级任务");
}];
blockOperation1.queuePriority = NSOperationQueuePriorityLow;
NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"高优先级任务");
sleep(1);
}];
blockOperation2.queuePriority = NSOperationQueuePriorityHigh;
NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
}];
[blockOperation1 addDependency:blockOperation3];
[blockOperation2 addDependency:blockOperation3];
[operationQueue addOperation:blockOperation1];
[operationQueue addOperation:blockOperation2];
[operationQueue addOperation:blockOperation3];
打印结果:
2018-03-16 18:33:52.435983+0800 test[6880:656581] 高优先级任务
2018-03-16 18:33:53.437498+0800 test[6880:656579] 低优先级任务
为了比较、我们可以把依赖去掉。这样执行顺序就会按照代码添加的顺序执行了。
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount = 1;
NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"低优先级任务");
}];
blockOperation1.queuePriority = NSOperationQueuePriorityLow;
NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"高优先级任务");
sleep(1);
}];
blockOperation2.queuePriority = NSOperationQueuePriorityHigh;
[operationQueue addOperation:blockOperation1];
[operationQueue addOperation:blockOperation2];
打印结果
2018-03-16 18:30:40.293723+0800 test[6825:651558] 低优先级任务
2018-03-16 18:30:40.294005+0800 test[6825:651555] 高优先级任务
阻塞
-
操作阻塞
- (void)waitUntilFinished;
阻塞当前线程、直到该操作执行完成。
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"操作3执行完毕");
}];
NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
NSLog(@"操作2开始执行");
[blockOperation3 waitUntilFinished];
NSLog(@"操作2执行完毕");
}];
[operationQueue addOperation:blockOperation2];
[operationQueue addOperation:blockOperation3];
打印结果
2018-03-19 10:40:11.856272+0800 test[1611:101411] 操作2开始执行
2018-03-19 10:40:14.857841+0800 test[1611:101413] 操作3执行完毕
2018-03-19 10:40:14.858028+0800 test[1611:101411] 操作2执行完毕
-
队列阻塞
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
- 如果为YES。阻塞当前线程、直到队列该次添加的所有操作全部执行完成。
- 如果为NO。就是批量添加操作而已。
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
sleep(3);
NSLog(@"操作3执行完毕");
}];
NSLog(@"添加操作");
[operationQueue addOperations:@[blockOperation3] waitUntilFinished:YES];
NSLog(@"添加完成");
打印结果:
2018-03-19 10:43:58.998232+0800 test[1690:108032] 添加操作
2018-03-19 10:44:02.000517+0800 test[1690:108155] 操作3执行完毕
2018-03-19 10:44:02.001007+0800 test[1690:108032] 添加完成
-
线程/队列死锁:
这个不太好用语言描述。当阻塞生效时、若所依赖的操作无法并发完成。线程/队列将被锁死。举个例子:
NSOperationQueue *operationQueue=[NSOperationQueue mainQueue];
//pat1
[operationQueue addOperations:@[blockOperation3] waitUntilFinished:YES];
//blockOperation3的执行相当于被添加到最后
//pat2
blockOperation3();
最后的这个pat2将永远不会执行。因为主线程的并发为1、而这个1正在被占用。
pat1
等待pat2
执行完毕。pat2
又在等待着pat2
执行完毕。造成死锁。
如果operationQueue
的并发数为2、pat2将会被放到另一个线程去执行、执行完毕解锁当前线程。就不会出现死锁的问题。
NSOperation的API
大概就一下这些东西、想看怎么用可以去开篇的帖子。或者自己搜搜。
- 操作:
可以KVC的状态(取消、进行、准备就绪、完成)/阻塞线程/completion回调/移除依赖等等....
@interface NSOperation : NSObject {
@private
id _private;
int32_t _private1;
#if __LP64__
int32_t _private1b;
#endif
}
// 开始操作
- (void)start;
// 操作任务的入口,一般用于自定义NSOperation的子类
- (void)main;
// 判断是否已经被取消
@property (readonly, getter=isCancelled) BOOL cancelled;
// 取消操作Operation,调用后不会自动马上取消,需要通过isCancelled方法检查是否被取消,然后自己编写代码退出当前的Operation
- (void)cancel;
// 是否正在执行
@property (readonly, getter=isExecuting) BOOL executing;
// 是否执行完
@property (readonly, getter=isFinished) BOOL finished;
// 判定该线程是否是并发线程,即调用该operation的start方法的线程是否与operation所在线程相同
// 注意:此属性即将被弃用,之后使用asynchronous属性代替
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
// 是否异步执行
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);
// 是否准备好
// 在start方法开始之前,需要确定Operation是否准备好,默认为YES,如果该operation没有准备好,则不会start。
@property (readonly, getter=isReady) BOOL ready;
// 添加依赖关系,如:[op1 addDependency:op2]; op2先执行,op1后执行
- (void)addDependency:(NSOperation *)op;
// 取消依赖,注意:操作对象的依赖不能在操作队列执行时取消
- (void)removeDependency:(NSOperation *)op;
// 获取有依赖关系的Operation所组成的数组
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
// Operation优先级的枚举
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
// 优先级
@property NSOperationQueuePriority queuePriority;
// Operation完成后调用的代码块
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);
// 堵塞当前线程,直到该Operation执行结束,才会执行接下来的代码
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);
// 设定Operation的线程优先级,取值范围0~1,默认为0.5
// 即使设定了线程优先级,也只能保证其在该线程的main()方法范围内有效,Operation的其他代码仍然执行在默认线程
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);
// 用于系统自动合理的管理队列的资源分配。
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
// 操作任务的名字
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);
@end
需要注意的是如果一个操作的状态为取消、进行、完成。是不可以被添加进队列的。
- 对于队列:
// 默认最大操作数为-1、也就是由系统分配
static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1;
@interface NSOperationQueue : NSObject {
@private
id _private;
void *_reserved;
}
// 添加操作对象(NSOperation对象)
- (void)addOperation:(NSOperation *)op;
// 添加操作对象组(NSOperation对象),waitUntilFinished:是否阻塞当前线程,等待所有操作都完成
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
// 添加操作任务
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
// 获取操作任务对象组
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
// 获取操作任务对象总数
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
// 最大并发数
@property NSInteger maxConcurrentOperationCount;
// 是否暂停队列
@property (getter=isSuspended) BOOL suspended;
// 队列名字
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);
// 用于系统自动合理的管理队列的资源分配
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
// underlyingQueue属性的值是主线程的调度队列,此属性不能设置为其它值
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
// 取消所有操作任务
- (void)cancelAllOperations;
// 阻塞当前线程,等待所有操作执行完毕
- (void)waitUntilAllOperationsAreFinished;
// 获取当前操作队列
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
// 获取主操作队列
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);
@end
最后
本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。