并发 描述的概念是同时有多个任务在执行,这些任务在单核CPU上以分时的形式在运行(宏观上可以看成多个任务同时执行),而在多核CPU上才是真正的同时执行。在iOS开发中,Apple提供了四种**API(pthread,NSThread,GCD,NSOperation)**用于并发编程。本文以讲解四种API的使用方法为切入点,带你了解多线程的全貌。
在学习并发编程之前,首先我们要知道线程的概念,以及线程的一些相关基础知识,这是学习并发编程的基础。
什么是线程
线程(thread)是进程的基本单位,一个进程包含一个或多个线程(至少包含一个),操作系统的调度器可以直接调度线程。所有的并发编程API都是构建在线程上的。
进程和线程的区别:
1,进程是操作系统资源分配
的最小单位。线程是CPU调度
的最小单位。(进程有独立的资源和内存而线程没有)
2,进程有独立的地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护,这种操作非常昂贵。而线程是共享进程中的数据,使用相同的地址空间,因此CPU切换一个线程的花费远比进程小的多,同时创建一个线程的开销也比进程要小的多。
3,线程间通信更方便,同一进程下的线程共享全局变量
、静态变量
的数据。
注意:1,iOS不能主动通过代码创建进程fork()
。2,iOS一个程序就是一个进程,进程间不会相互影响,一个线程挂掉会导致整个进程挂掉。
线程的状态
新建:线程对象在内存中被创建出来。
就绪:除了CPU以外的所有资源都已分配好了,等待CPU调度。(已经加入可调度线程池中)
运行:CPU正在运行该线程。(当CPU切换执行其它线程的时候,当前线程退回到就绪状态)
阻塞:除了CPU以外,还有其它资源没有分配,(阻塞状态的线程,被移出了可调度线程池)
死亡:线程被销毁,占用的内存空间被释放。
并发编程的四种API介绍
pthread
pthread(posix线程) 是一套纯C的API,平时几乎不会使用,如果没有兴趣可以不用了解,当然如果想多掌握一些的话,可以继续往下阅读。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 如何使用: 1,导入头文件 #import <pthread.h> 2,调用函数 int pthread_create(pthread_t * __restrict,pthread_attr_t * __restrict,void * (*) (void *),void * __restrict);
/*参数解释 返回值是0 表示创建线程成功 非0表示创建线程失败
1,pthread_t * 线程的标示 2,pthread_attr_t 线程的属性 3,void* (*) (void *) 返回值 函数名 参数 函数签名 void * 约等于OC中的id任意类型 4,void * 给函数的参数 */
|
NSThread
NSThread是pthread面向对象的封装。在面向对象的编程环境中,使用也更加习惯方便。一个NSThread实例对应一个线程。
一 如何使用:
【类方法/自动开启】
优点:使用简单,线程创建好之后处于就绪(runnable)状态,CPU可直接调度。
缺点:无法拿到线程对象本身做更详细的设置。
1 2
| + (void)detachNewThreadWithBlock:(void (^)(void))block; + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
|
【实例方法/手动开启】
优点:可以获取到线程对象,做更详细的设置。(设置线程优先级、线程名称等)
缺点:线程创建好以后处于新建(new)状态,需要手动调用start/main方法进入就绪(runnable)状态,没有类方法使用简单。
1 2 3 4 5
| - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument; - (instancetype)initWithBlock:(void (^)(void))block;
- (void)start; // start方法在子线程执行,不能重复开启(不能调用两遍以上start方法) - (void)main; // main方法在主线程执行,可以重复调用
|
注意:- (instancetype)init;方法得到的是主线程。
【隐式创建】
优缺点和类方法相同
1
| - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
|
注意:隐式创建方法不属于NSThread,而是属于NSObject的NSThreadPerformAdditions分类。
【自定义】
1 2 3
| 第一步:自定义一个线程类。例如XXThread,继承自NSThread类。 第二步:在XXThread类中,重写main方法,把需要在后台子线程中执行的代码放进去。 第三步:通过alloc+init方法创建XXThread对象,然后调用start方法即可。
|
二 常见属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| // 线程优先级,double类型,取值范围是:0.0~1.0,默认是0.5,1.0表示优先级最高,优先级越高,表示被CPU调度的概率越高。逐渐被废弃,用qualityOfService替代 thread.threadPriority
// 线程服务质量约等于线程优先级,枚举值,默认是NSQualityOfServiceDefault=-1,优先级最低。iOS8之后比threadPriority好用 thread.qualityOfService;
// 线程的名称,主线程默认名称是main,子线程默认名称为null, thread.name;
// 判断当前线程是否是主线程,只读属性 mainThread
// 判断当前线程是否正在执行任务,只读属性 executing
// 判断当前线程是否执行结束,只读属性 finished
// 判断当前线程是否被取消,只读属性 cancelled
// 线程在stack栈上占用的存储空间,通常为512kb,线程释放后,空间收回。 thread.stackSize;
|
三 常用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // 休眠到指定时间点 + (void)sleepUntilDate:(NSDate *)date // 休眠一段时间 + (void)sleepForTimeInterval:(NSTimeInterval)time
// 获取当前线程对象 [NSThread currentThread]; // 获取主线程对象 [NSThread mainThread];
// 退出线程 [NSThread exit];
// 如果自定义线程类,把要执行的任务,放在重写的main方法中。 - (void)main // thread body method
|
直接操作线程对象可能引发一个问题,如果你的代码和项目中使用到的框架代码都创建了自己的线程时,那么活动的线程数量有可能以指数级增长。这在大型项目中是一个常见问题。例如:在8核CPU中,你创建了8个线程来发挥CPU性能。然而在这些线程中调用到的框架代码也做了同样的事情(因为框架不知道你也创建了线程),这样就会很快产生成百上千的线程,代码每个部分自身都没有问题,但是最终还是产生了问题。每个线程都会消耗一些内存和内核资源。
接下来,我将介绍并发编程API:GCD,它们是通过管理一个可以协同使用的线程池,来解决上面的问题的。
GCD
GCD(Grand Central Dispatch)是多核优化的解决方案,是一套纯C的API,核心思想是在系统底层维护了一个线程池,自动管理线程对象,开发者不用直接和线程打交道,只需要关注队列和任务即可。
在学习GCD编程之前,首先我们要理解队列和任务的概念,这是基础,也是GCD的核心概念。
队列
用来存放任务的,先进先出的(FIFO,First in first out)容器就是队列。
队列分为两类:串型队列(Serial Queue)和并发队列(Concurrent Queue)。
【串行队列】
内部的任务一个接着一个执行,等待上一个任务执行完毕,才会接着执行下一个任务。
1 2 3 4 5 6 7 8
| // 获取串行队列的方式 1.系统提供 2.手动创建 // 1.获取系统提供的串行队列 dispatch_queue_t queue = dispatch_get_main_queue();
// 2.创建一个串行队列 // 队列的名称com.app.serial // 队列的类型,是枚举值。DISPATCH_QUEUE_SERIAL表示串行 dispatch_queue_t queue = dispatch_queue_create("com.app.serial", DISPATCH_QUEUE_SERIAL);
|
【并发队列】
队列内部的多个任务同一时刻执行。
1 2 3 4 5 6 7 8 9 10
| // 获取并发队列的方式 1.系统提供 2.手动创建 // 1.获取系统提供的并发队列 // 队列优先级 DISPATCH_QUEUE_PRIORITY_DEFAULT // 第二个参数flags作为保留字段备用,一般都直接填0 dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 2.创建一个并发队列 // 队列的名称。 作用:可以协助开发调试和崩溃报告分析 // 队列的类型,是枚举值。DISPATCH_QUEUE_CONCURRENT表示并发。 dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT);
|
【主队列】也叫【全局串行队列】
主队列是特殊的串行队列,由系统提供,使用的时候只要去获取即可,无需开发者手动创建,特殊点在于主队列中的任务只能由主线程去执行。
特点:
1,主线程正在执行代码暂时不调度任务,等主线程执行结束后再调度任务。这是主队列中同步执行导致死锁的原因。
2,UI更新的操作都要放在主线程中处理。
1 2
| // 获取系统提供的主队列 dispatch_queue_t queue = dispatch_get_main_queue();
|
【全局并发队列】
全局并发队列,使用的时候去获取即可,无需开发者手动创建。
1 2 3 4 5 6 7 8 9
| // 获取系统提供的全局并发队列 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); // 参数解释 1,指定队列的优先级。 DISPATCH_QUEUE_PRIORITY_HIGH (2) DISPATCH_QUEUE_PRIORITY_DEFAULT (0)// 自定义队列的优先级都是默认优先级 DISPATCH_QUEUE_PRIORITY_LOW (-2) DISPATCH_QUEUE_PRIORITY_BACKGROUND (INT16_MIN) 2,作为保留字段备用(一般为0)
|
任务
添加到队列中的可执行代码块就是任务。
注意:dispatch_block_t的定义是typedef void (^dispatch_block_t)(void),
由定义可以看出它本质就是一个无参数无返回值的block。
任务添加到队列的方式
任务添加到队列的方式有两个:同步执行(dispatch_sync)和异步执行(dispatch_async),两者区别在于是否开启新线程,同步执行不开启新线程,任务在当前线程执行,异步开启新线程,任务在新线程执行。
【同步执行】
1 2 3 4 5 6 7 8
| // 队列 dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT); // 任务 dispatch_block_t block = ^ { [NSThread sleepForTimeInterval:2.f]; NSLog(@"%@", [NSThread currentThread]); }; dispatch_sync(queue, block);
|
【异步执行】
1 2 3 4 5 6 7 8 9
| // 队列 dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT); // 任务 dispatch_block_t block = ^ { [NSThread sleepForTimeInterval:2.f]; NSLog(@"%@", [NSThread currentThread]); }; dispatch_async(queue, block);
|
上面这么写的目的,是帮助理解,在开发中代码要简写。
1 2 3 4 5 6 7 8
| // 1,创建串行队列 dispatch_queue_t queue = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL); // // 2,异步执行 dispatch_async(queue, ^{ NSLog(@"%@",[NSThread currentThread]); });
|
在了解完GCD的核心基本概念之后,我们学习GCD的使用方法
串型队列的执行
【串行队列➕同步执行】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| - (void)syncAndSerial { NSLog(@"__BEGIN:%@__", [NSThread currentThread]); dispatch_queue_t queue = dispatch_queue_create("com.app.serial", DISPATCH_QUEUE_SERIAL); // 第一个任务 dispatch_sync(queue, ^{ // 这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第一个任务---当前线程%@", [NSThread currentThread]); }); // 第二个任务 dispatch_sync(queue, ^{ // 这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第二个任务---当前线程%@", [NSThread currentThread]); }); // 第三个任务 dispatch_sync(queue, ^{ // 这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第三个任务---当前线程%@", [NSThread currentThread]); }); NSLog(@"__END:%@__", [NSThread currentThread]); }
|
log信息显示如下:
1 2 3 4 5 6
| __BEGIN:<NSThread: 0x6000032ec0c0>{number = 1, name = main}__ ----执行第一个任务---当前线程<NSThread: 0x6000032ec0c0>{number = 1, name = main} ----执行第二个任务---当前线程<NSThread: 0x6000032ec0c0>{number = 1, name = main} ----执行第三个任务---当前线程<NSThread: 0x6000032ec0c0>{number = 1, name = main} __END:<NSThread: 0x6000032ec0c0>{number = 1, name = main}__
|
执行过程:当前线程执行到第一个任务时,会把任务添加到串行队列中,由于是同步的,必须再把任务执行完成,才可以继续向下执行第二个任务。
结论:同步执行 ➕ 串行队列,不开启新线程,在当前线程中依次执行。
【串行队列➕异步执行】
开启一个子线程,任务在子线程执行,为什么只开启一个,因为是串行队列,只有一个线程就可以按顺序执行队列中的所有任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| - (void)asyncAndSerial { NSLog(@"__BEGIN:%@__", [NSThread currentThread]); dispatch_queue_t queue = dispatch_queue_create("com.app.serial", DISPATCH_QUEUE_SERIAL); // 第一个任务 dispatch_async(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第一个任务---当前线程%@",[NSThread currentThread]); }); // 第二个任务 dispatch_async(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第二个任务---当前线程%@",[NSThread currentThread]); }); // 第三个任务 dispatch_async(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第三个任务---当前线程%@",[NSThread currentThread]); }); NSLog(@"__END:%@__", [NSThread currentThread]); }
|
log信息显示如下:
1 2 3 4 5
| __BEGIN:<NSThread: 0x6000032ec0c0>{number = 1, name = main}__ __END:<NSThread: 0x6000032ec0c0>{number = 1, name = main}__ ----执行第一个任务---当前线程<NSThread: 0x6000032a2300>{number = 3, name = (null)} ----执行第二个任务---当前线程<NSThread: 0x6000032a2300>{number = 3, name = (null)} ----执行第三个任务---当前线程<NSThread: 0x6000032a2300>{number = 3, name = (null)}
|
结论:异步执行 ➕ 串行队列,开启一个新线程,任务依次执行。
并发队列的执行
【并发队列➕同步执行】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| - (void)syncAndConcurrent { NSLog(@"__BEGIN:%@__", [NSThread currentThread]); dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第一个任务---当前线程%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第二个任务---当前线程%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第三个任务---当前线程%@",[NSThread currentThread]); }); NSLog(@"__END:%@__", [NSThread currentThread]); }
|
log信息显示如下:
1 2 3 4 5 6
| __BEGIN:<NSThread: 0x6000032ec0c0>{number = 1, name = main}__ ----执行第一个任务---当前线程<NSThread: 0x6000032ec0c0>{number = 1, name = main} ----执行第二个任务---当前线程<NSThread: 0x6000032ec0c0>{number = 1, name = main} ----执行第三个任务---当前线程<NSThread: 0x6000032ec0c0>{number = 1, name = main} __END:<NSThread: 0x6000032ec0c0>{number = 1, name = main}__
|
结论:执行情况与同步执行 ➕ 串行队列一模一样,不开启新线程,在当前线程中依次执行。
【并发队列➕异步执行】
开启多个子线程,任务在子线程执行,具体开启多少个线程是不固定的(线程数,不由我们控制是由gcd来决定的)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| - (void)asyncAndConcurrent { NSLog(@"__BEGIN:%@__", [NSThread currentThread]); dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT); // 第一个任务 dispatch_async(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第一个任务---当前线程%@",[NSThread currentThread]); }); // 第二个任务 dispatch_async(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第二个任务---当前线程%@",[NSThread currentThread]); }); // 第三个任务 dispatch_async(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第三个任务---当前线程%@",[NSThread currentThread]); }); NSLog(@"__END:%@__", [NSThread currentThread]); }
|
log信息显示如下:
1 2 3 4 5
| __BEGIN:<NSThread: 0x6000032ec0c0>{number = 1, name = main}__ __END:<NSThread: 0x6000032ec0c0>{number = 1, name = main}__ ----执行第二个任务---当前线程<NSThread: 0x60000328cb40>{number = 11, name = (null)} ----执行第三个任务---当前线程<NSThread: 0x6000032bb2c0>{number = 12, name = (null)} ----执行第一个任务---当前线程<NSThread: 0x6000032b2380>{number = 8, name = (null)}
|
结论:开启若干新线程,任务并发执行。
主队列的执行
系统已经为我们提供了2个常用队列,主队列(全局串行队列)和全局并发队列,开发中很多时候都是使用系统提供的队列,不用自己手动创建
【主队列➕同步执行】
【主队列➕异步执行】
不开线程,因为主队列的任务只有主线程可以执行。同步执行任务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| - (void)asyncAndMain { NSLog(@"__BEGIN:%@__", [NSThread currentThread]); dispatch_queue_t queue = dispatch_get_main_queue(); // 第一个任务 dispatch_async(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第一个任务---当前线程%@",[NSThread currentThread]); }); // 第二个任务 dispatch_async(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第二个任务---当前线程%@",[NSThread currentThread]); }); // 第三个任务 dispatch_async(queue, ^{ //这里线程暂停2秒,模拟一般的任务的耗时操作 [NSThread sleepForTimeInterval:2]; NSLog(@"----执行第三个任务---当前线程%@",[NSThread currentThread]); }); NSLog(@"__END:%@__", [NSThread currentThread]); }
|
log信息显示如下:
1 2 3 4 5
| __BEGIN:<NSThread: 0x600002ba4900>{number = 1, name = main}__ __END:<NSThread: 0x600002ba4900>{number = 1, name = main}__ ----执行第一个任务---当前线程<NSThread: 0x600002ba4900>{number = 1, name = main} ----执行第二个任务---当前线程<NSThread: 0x600002ba4900>{number = 1, name = main} ----执行第三个任务---当前线程<NSThread: 0x600002ba4900>{number = 1, name = main}
|
队列的区别比较表格
|
并发队列 |
串行队列 |
主队列 |
同步(sync) |
在当前线程,依次执行任务 |
在当前线程,依次执行任务 |
死锁 |
异步(async) |
开启新线程,并行执行任务 |
开启新线程,依次执行任务 |
在当前线程,依次执行任务 |
NSOperation
NSOperation是GCD面向对象的封装,在面向对象的编程环境中使用更加习惯方便,是苹果官方推荐的安全可靠的并发编程方式。
首先介绍两个核心类NSOperationQueue和NSOperation。
NSOperationQueue
- 通过NSOperationQueue可以获取到主队列,也可以创建新队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| - (void)queueKinds { // 获取主队列 NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{ NSLog(@"mainQueue1::%@", [NSThread currentThread]); }]; [mainQueue addOperationWithBlock:^{ NSLog(@"mainQueue2::%@", [NSThread currentThread]); }]; // 创建新队列 NSOperationQueue *customQueue = [[NSOperationQueue alloc] init];
[customQueue addOperationWithBlock:^{ NSLog(@"customQueue::%@", [NSThread currentThread]); }]; [customQueue addOperationWithBlock:^{ NSLog(@"customQueue1::%@", [NSThread currentThread]); }]; [customQueue addOperationWithBlock:^{ NSLog(@"customQueue2::%@", [NSThread currentThread]); }]; }
|
log信息显示如下:
1 2 3 4 5
| customQueue2::<NSThread: 0x600003df4000>{number = 5, name = (null)} customQueue1::<NSThread: 0x600003df4080>{number = 4, name = (null)} customQueue::<NSThread: 0x600003df9b40>{number = 7, name = (null)} mainQueue1::<NSThread: 0x600003db00c0>{number = 1, name = main} mainQueue2::<NSThread: 0x600003db00c0>{number = 1, name = main}
|
结论:通过alloc+init创建的队列是并发队列。
- 通过控制最大并发数属性maxConcurrentOperationCount,可以实现串行和并发的切换。默认情况下为-1,表示不进行限制,队列是并发队列。为1时,队列为串行队列。大于1时,队列为并发队列。当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为min{自己设定的值,系统设定的默认最大值}。
NSOperation
NSOperation是个抽象类,不能够直接使用,系统提供了两个子类NSInvocationOperation和NSBlockOperation供使用,也可以自定义子类。
【NSInvocationOperation】
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| - (void)operation { // 1,创建 NSInvocationOperation 对象 NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(taskMethod:) object:@2]; // 2,不开新线程,在当前线程开始执行操作 [op1 start]; // [op1 main]; main方法和start作用相同 }
- (void)taskMethod:(NSNumber *)count { for (int i = 0; i < count.intValue; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"---%@", [NSThread currentThread]); // 打印当前线程 } }
|
1 2 3 4 5 6 7 8 9 10 11
| - (void)demo { // 默认最大并发数是-1,队列属于并发队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; for (int i = 0; i < 6; ++i) { // 创建操作 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector: @selector(taskMethod:) object:@1]; // 操作添加到队列 [queue addOperation: operation]; } }
|
log信息显示如下:
1 2 3 4 5 6
| ---<NSThread: 0x6000030a0500>{number = 5, name = (null)} ---<NSThread: 0x6000030a18c0>{number = 4, name = (null)} ---<NSThread: 0x6000030dbd80>{number = 3, name = (null)} ---<NSThread: 0x600003082200>{number = 6, name = (null)} ---<NSThread: 0x6000030a9640>{number = 8, name = (null)} ---<NSThread: 0x6000030a9f40>{number = 7, name = (null)}
|
【NSBlockOperation】
相比NSInvocationOperation使用起来更加简便,所以开发中经常使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| - (void)demo { // 1.创建 NSBlockOperation 对象 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程 } }]; // 2.添加额外的操作 [op addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程 } }]; [op addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"3---%@", [NSThread currentThread]); // 打印当前线程 } }]; [op addExecutionBlock:^{ for (int i = 0; i < 2; i++) { [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"4---%@", [NSThread currentThread]); // 打印当前线程 } }]; // 3.调用 start 方法开始执行操作 [op start]; }
|
1 2 3 4 5 6 7 8 9
| 4---<NSThread: 0x6000037aacc0>{number = 7, name = (null)} 1---<NSThread: 0x6000037f0840>{number = 1, name = main} 3---<NSThread: 0x6000037b8700>{number = 5, name = (null)} 2---<NSThread: 0x6000037bc000>{number = 6, name = (null)} 2---<NSThread: 0x6000037bc000>{number = 6, name = (null)} 4---<NSThread: 0x6000037aacc0>{number = 7, name = (null)} 3---<NSThread: 0x6000037b8700>{number = 5, name = (null)} 1---<NSThread: 0x6000037f0840>{number = 1, name = main}
|
1 2 3 4 5 6 7
| // 默认最大并发数是-1,队列属于并发队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 1.创建 NSBlockOperation 对象 NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"%@",[NSThread currentThread]); }]; [queue addOperation:op];
|
简便写法
1 2 3 4 5 6
| // 队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 添加操作 [queue addOperationWithBlock:^{ NSLog(@"%@",[NSThread currentThread]); }];
|
在NSOperation单独使用情况下,调用start/main方法,是否开启新线程,取决于操作的个数。如果操作的个数是一个的话,任务在当前线程执行。如果操作个数大于等于两个,就会自动开启新线程。
【NSInvocationOperation与NSBlockOperation异同】
共同点: NSInvocationOperation类只能封装一个操作,所以当调用start方法时,任务在当前线程执行。NSBlockOperation是否开启新线程,取决于操作的个数。如果添加的操作的个数是一个的话,调用start方法时,任务在当前线程执行。如果添加的操作个数大于等于两个,就会自动开启新线程。NSCustomOperation调用start方法时,任务在当前线程执行。
区别: NSBlockOperation可以添加多个操作。而NSInvocationOperation和NSCustomOperation只能添加一个操作。
接下来再介绍NSOperationQueue的一些其它的特性和用法。
【最大并发数】
队列通过控制最大并发数属性,可以实现串行与并发的切换功能。
maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,是并发队列。为1时,队列为串行队列。大于1时,队列为并发队列。当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为min{自己设定的值,系统设定的默认最大值}。
【优先级】
队列的优先级
iOS8.0之后队列优先级使用qualityOfService属性
1 2 3
| NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 优先级高代表被执行的概率高,而不是一定会先执行。 queue.qualityOfService = NSQualityOfServiceUserInteractive;
|
任务的优先级
iOS8.0之后任务优先级使用qualityOfService属性替代queuePriority属性
1 2 3 4 5
| NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ }]; //优先级高代表被执行的概率高,而不是一定会先执行。 //qualityOfService的值是枚举类型,可到文档中具体查看,这里不一一介绍了。 operation.qualityOfService = NSOperationQueuePriorityNormal;
|
【队列挂起和任务取消】
挂起:暂停队列中还没有被调度的任务,正在调度的任务,不能被暂停
取消队列中的所有操作,同样不会影响到正在执行中的操作
1 2 3 4 5 6 7 8
| NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; // 挂起 暂停队列中还没有被调度的任务,正在调度的任务,不能被暂停 [operationQueue setSuspended:YES]; // 取消挂起 线程继续调度任务执行 [operationQueue setSuspended:NO];
// 取消队列中的所有操作,同样不会影响到正在执行中的操作 [operationQueue cancelAllOperations];
|
【队列的操作数】
队列的操作数属性可以获取队列中操作的数量
1 2 3
| NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init]; // operationCount属性可以获取当前队列中的任务数量 NSLog(@"%@",operationQueue.operationCount);
|