iOS多线程编程
基本知识
1. 进程(process)
- 进程是指在系统中正在运行的一个应用程序,就是一段程序的执行过程。
- 每个进程之间是相互独立的, 每个进程均运行在其专用且受保护的内存空间内。
进程是一个具有一定独立功能的程序关于某次数据集合的一次运行活动,它是操作系统分配资源的基本单元。 - 进程状态:进程有三个状态,就绪,运行和阻塞。就绪状态其实就是获取了除cpu外的所有资源,只要处理器分配资源马上就可以运行。运行态就是获取了处理器分配的资源,程序开始执行,阻塞态,当程序条件不够时,需要等待条件满足时候才能执行,如等待I/O操作的时候,此刻的状态就叫阻塞态。
2. 线程(thread)
- 一个进程要想执行任务,必须要有线程,至少有一条线程
- 一个进程的所有任务都是在线程中执行
- 每个应用程序想要跑起来,最少也要有一条线程存在,其实应用程序启动的时候我们的系统就会默认帮我们的应用程序开启一条线程,这条线程也叫做'主线程',或者'UI线程'
3. 进程和线程的关系
- 线程是进程的执行单元,进程的所有任务都在线程中执行!
- 线程是 CPU 调用的最小单位
- 进程是 CPU 分配资源和调度的单位
- 一个程序可以对应过个进程,一个进程中可有多个线程,但至少要有一条线程
- 同一个进程内的线程共享进程资源
相同点
进程和线程都是有操作系统所提供的程序运行的基本单元,系统利用该基本单元实现系统对应用程序的并发性
不同点
- 进程和线程的主要差别在于他们是不同的操作系统资源管理方式。
- 进程有独立的地址空间,一个进程crash后,在保护模式下不会对其他进程产生影响。
- 而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间。一个线程crash就等于整个进程crash
- 多进程的程序比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
优缺点
- 进程执行开销大,但利于资源的管理和保护
- 线程执行开销小,但不利于资源的管理的和保护,线程适合于在SMP(多核处理机)机器上运行
4.
多线程
- Mac、iPhone的操作系统OS X、iOS根据用户的指示启动应用程序后,首先便将包含在应用程序中的CPU命令列配置到内存中。CPU从应用程序知道的地址开始,一个一个执行CPU命令列。
-
在OC的if或for语句等控制语句或函数调用的情况下,执行命令列的地址会远离当前的位置(位置迁移)。但是,由于一个CPU一次只能执行一个命令,不能执行某处分开的并列的两个命令,因此通过CPU执行的CPU命令列就好比一条无分叉的大道,其执行不会出现分歧。
这里所说的“1个CPU执行的CPU命令列为一条无分叉路径”,即为“线程”
-
这种无分叉路径不只1条,存在有多条时即为“多线程”。 1个CPU核执行多条不同路径上的不同命令。
OS X和iOS的核心XNU内核在发生操作系统事件时(如每隔一定时间,唤起系统调用等情况)会切换执行路径。例如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令列。这被称为“上下文切换”
由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列地执行多个线程一样。而且在具有多个CPU核的情况下,就不是“看上去像”了,而是真的提供了多个CPU核并行执行多个线程的技术。这种利用多线程编程的技术就被称为“多线程编程”
但是,多线程编程实际上是一种易发生各种问题的编程技术。比如多个线程更新相同资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程相互等待(死锁)、使用太多线程会消耗大量内存等。
5. 多线程的优缺点
优点
- 能适当的提高程序的执行效率
- 能适当提高资源利用
缺点
- 开启线程需要占用一定的内存空间,如果开启大量的线程,则会占用大量的内存空间,降低程序的性能
- 线程越多,CPU在调度线程上的开销就越大
- 程序设计更加复杂:比如线程之间的通信,多线程的数据共享
6. 多线程实际应用
- 使用单例模式时,可以使用GCD
- 耗时操作放入线程处理,完成后回主线程操作
- 从数据库读取大量数据,可开辟子线程操作
- 处理音频、视频数据时,在子线程处理
- 数据同步操作,如百度云,可在子线程进入后台后开始同步
7. 主线程
- 也就是应用程序启动的时候,系统默认帮我们创建的线程,称之为‘主线程’或者是‘UI线程’;
- 主线程的作用一般都是用来显示或者刷新UI界面例如:点击,滚动,拖拽等事件
8. 串行(Serial)和并行(Parallelism)
串行和并行描述的是任务和任务之间的执行方式. 串行是任务A执行完了任务B才能执行, 它们俩只能顺序执行. 并行则是任务A和任务B可以同时执行.
9. 同步(Synchronous)和异步(Asynchronous)
- 同步和异步描述的其实就是函数什么时候返回
- 比如用下载图片的函数A: {download image}
同步函数只有在image下载结束之后才返回, 下载的这段时间函数A只能搬个小板凳在那儿坐等...
而异步函数, 立即返回. 图片会去下载, 但函数A不会去等它完成. So, 异步函数不会堵塞当前线程去执行下一个函数!
10. 并发(Concurrency)和并行(Parallelism)
用Ray大神的示意图和说明来解释一下:
并发是程序的属性(property of the program), 而并行是计算机的属性(property of the machine).
再来解释一下, 并行和并发都是用来让不同的任务可以"同时执行", 只是并发是伪同时, 而并行是真同时. 假设你有任务T1和任务T2(这里的任务可以是进程也可以是线程):
- 首先如果你的CPU是单核的, 为了实现"同时"执行T1和T2, 那只能分时执行, CPU执行一会儿T1后马上再去执行T2, 切换的速度非常快(这里的切换也是需要消耗资源的, context switch), 以至于你以为T1和T2是同时执行了(但其实同一时刻只有一个任务占有着CPU).
- 如果你是多核CPU, 那么恭喜你, 你可以真正同时执行T1和T2了, 在同一时刻CPU的核心core1执行着T1, 然后core2执行着T2, great!
其实我们平常说的并发编程包括狭义上的"并行"和"并发", 你不能保证你的代码会被并行执行, 但你可以以并发的方式设计你的代码. 系统会判断在某一个时刻是否有可用的core(多核CPU核心), 如果有就并行(parallelism)执行, 否则就用context switch来分时并发(concurrency)执行.
Parallelism requires Concurrency, but Concurrency does not guarantee Parallelism!(并行要求并发性,但并发并不能保证并行性)
iOS中的多线程
类型 | 简介 | 实现语言 | 线程生命周期 | 使用频率 |
---|---|---|---|---|
pthread | 1.一套通用的多线程API 2.使用与Unix、Linux/Windows等系统 3.夸平台、可移植 4.使用难度大 | C | 程序员管理 | 几乎不用 |
NSThread | 1.使用更加面向对象 2.简单易用,可直接操作线程对象 | OC | 程序员管理 | 偶尔使用 |
GCD | 1.旨在替代NSThread等线程技术 2.充分利用设备的多核 3.基于C的底层的API | C | 自动管理 | 经常使用 |
NSOperation | 1.是基于GCD实现的Objective-C API 2.比GCD多了一些更简单实用的功能 3.使用更加面向对象 | OC | 自动管理 | 经常使用 |
iOS编程技术一-NSThread
创建线程的方式
-
通过NSThread的实例方法
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
-
通过NSThread的类方法
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObjects:(nullable id)argument;
-
通过NSObject的分类方法
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg
NSThread代码Demo
- (IBAction)downloadAction:(UIButton *)sender {
[self objectNSthreadMethod];
}
//通过NSObject的分类方法开辟线程
- (void)categoryNSthreadMethod {
[self performSelectorInBackground:@selector(downloadImage) withObject:nil];
}
//通过NSThread类方法开辟线程
- (void)classNSthreadMethod {
//异步1
[NSThread detachNewThreadSelector:@selector(downloadImage) toTarget:self withObject:nil];
//异步2
[NSThread detachNewThreadWithBlock:^{
[self downloadImage];
}];
}
//通过NSThread实例方法去下载图片
- (void)objectNSthreadMethod {
//创建一个程序去下载图片
// NSThread *thread = [[NSThread alloc] initWithBlock:^{
// [self downloadImage];
// }];
//or
NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage) object:nil];
//开启线程
[thread start];
thread.name = @"imageThread";
}
//下载图片
- (void)downloadImage {
NSURL * url = [NSURL URLWithString:@"https://ss3.baidu.com/-rVXeDTa2gU2pMbgoY3K/it/u=2573230712,1194340385&fm=202&mola=new&crop=v1"];
//线程延迟4s
[NSThread sleepForTimeInterval:4.0];
NSData * data = [NSData dataWithContentsOfURL:url];
NSLog(@"downloadImage:%@",[NSThread currentThread].description);//在子线程中下载图片
//在主线程中更新UI
[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
}
//更新ImageView
- (void)updateImage:(NSData *)data {
NSLog(@"updateImage:%@",[NSThread currentThread].description);//在主线程中更新UI
//将二进制数据转换为Image
UIImage *image = [UIImage imageWithData:data];
//设置image
self.imageView.image = image;
}
线程状态
1.新建状态
- 通过上面创建方法-initWithTarget...来实例化线程对象
- 程序还没开始运行线程中的代码
2.就绪状态
- 向线程对象发送 start 消息,线程对象被加入 可调度线程池 等待 CPU 调度
-
detachNewThreadSelector
方法detachNewThreadWithBlock
和performSelectorInBackground
方法会直接实例化一个线程对象并加入 可调度线程池 - 处于就绪状态的线程并不一定立即执行线程里的代码,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。
3.运行状态
- CPU 负责调度可调度线程池中线程的执行
- 线程执行完成之前(死亡之前),状态可能会在就绪和运行之间来回切换
- 就绪和运行之间的状态变化由 CPU 负责,程序员不能干预
4.阻塞状态
所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态
线程通过调用sleep方法进入睡眠状态
线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者
线程试图得到一个锁,而该锁正被其他线程持有
-
线程在等待某个触发条件
+ (void)sleepUntilDate:(NSDate *)date;//休眠到制定日期 + (void)sleepForTimeInterval:(NSTimeInterval)ti;//休眠指定时长 @synchronized(self):互斥锁 sleep(unsigned int);
5.死亡状态
- 正常死亡: 线程执行完毕
- 非正常死亡:
- 当满足某个条件后,在现程内部自己种中止执行(自杀)
- 当满足某个条件后,在主线程给其它线程打个死亡标记(下圣旨),让子线程自行了断.(被逼着死亡)
线程通信
线程在运行过程中,可能需要与其它线程进行通信,如在主线程中修改界面等等,可以使用如下接口:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)org waitUntilDone:(BOOL)wait
线程的属性
thread.name = @"imageThread";给线程添加一个名字,方便后面出现问题的追踪!
[NSThread currentThread];获取当前的进程,打印出来name和number,如果Number为1,则为主线程
thread.isMainThread;判断当前线程是否是主线程
[NSThread isMultiThreaded];判断进程当前是否是多线程
thread.threadPriority = 0.5;threadPriority是线程的优先级,最高是1.0,最低为0.0;默认我们创建的优先级是0.5;
thread.stackSize默认情况下主线程和子线程在栈区大小都是512k
-
线程执行状态
@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);//是否正在执行 @property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);//是否完成 @property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);//是否取消
线程的同步与锁
线程的同步与锁什么时候会发生呢?就在我们的线程共用资源的时候,导致资源竞争,举个例子:多个窗口同时售票系统
#import "SellTicketsViewController.h"
@interface SellTicketsViewController () {
NSInteger tickets;//总票数
NSInteger count;//当前卖出去的票数
}
@property (nonatomic, strong) NSThread* ticketsThreadOne;
@property (nonatomic, strong) NSThread* ticketsThreadTwo;
@property (nonatomic, strong) NSLock* ticketsLock;
@end
@implementation SellTicketsViewController
- (void)viewDidLoad {
[super viewDidLoad];
tickets = 100;
count = 0;
//锁对象
self.ticketsLock = [[NSLock alloc] init];
self.ticketsThreadOne = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
self.ticketsThreadOne.name = @"thread-1";
self.ticketsThreadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(sellAction) object:nil];
self.ticketsThreadTwo.name = @"thread-2";
}
- (IBAction)startSell:(id)sender {
[self.ticketsThreadOne start];
[self.ticketsThreadTwo start];
}
- (void)sellAction {
while (true) {
//上锁
[self.ticketsLock lock];
if (tickets > 0) {
[NSThread sleepForTimeInterval:0.5];
count = 100 - tickets;
NSLog(@"当前总票数是:%ld----->卖出:%ld------>线程名:%@",tickets,count,[NSThread currentThread]);
tickets --;
}else {
break;
}
//解锁
[self.ticketsLock unlock];
}
}
@end
通过上面的Demo应该理解线程的同步以及锁的使用问题
[myLock lock]
资源处理....
[myLock unLock];
iOS编程技术二-GCD
GCD基本介绍
- GCD(Grand Central Dispatch)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。
- GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,是完全面向过程的
任务和队列
任务
就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。
-
同步执行(sync):只能在当前线程中执行的任务,不具备开启新线程的能力
- 必须等待当前语句执行完毕,才会执行下一条语句
- 不会开启线程
- 在当前主线程执行 block 的任务
dispatch_sync(queue, block);
-
异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力
- 不用等待当前语句执行完毕,就可以执行下一条语句
- 会开启线程执行 block 的任务
- 异步是多线程的代名词
dispatch_async(queue, block);
队列
这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有四种队列:串行队列、并发队列、主队列、全局队列。
-
串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
- 一次只能"调度"一个任务
-
dispatch_queue_create("queue", NULL);
或者dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
-
并发队列(Concurrent Dispatch Queue):可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
- 一次可以"调度"多个任务
- 并发功能只有在异步(dispatch_async)函数下才有效
dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
-
主队列
- 专门用来在主线程上调度任务的队列
- 不会开启线程
- 在主线程空闲时才会调度队列中的任务在主线程执行
dispatch_get_main_queue();
-
全局队列
- 执行过程和并发队列一致,参考并发队列
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
小结: 在以后的使用中,记住下面的就可以了!
- 开不开线程由执行任务的函数决定
- 异步开,异步是多线程的代名词
- 同步不开
- 开几条线程由队列决定
- 串行队列开一条线程(GCD会开一条,NSOperation Queue最大并发数为1时也可能开多条)
- 并发队列开多条线程,具体能开的线程数量由底层线程池决定
GCD的使用
先来看一段代码:异步下载图片
//通过GCD异步下载图片
- (void)downloadImageByDispatchAsync{
self.imageView.image = nil;
//获取全局队列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//异步下载图片
NSURL *url = [NSURL URLWithString:@"https://ss3.baidu.com/-rVXeDTa2gU2pMbgoY3K/it/u=2573230712,1194340385&fm=202&mola=new&crop=v1"];
//获取资源转换为二进制
NSData * data = [NSData dataWithContentsOfURL:url];
//将二进制转为图片
UIImage *image = [UIImage imageWithData:data];
//获取主队列,更新UI
dispatch_async(dispatch_get_main_queue(), ^{
//给图控件赋值
self.imageView.image = image;
});
});
}
和NSThread对比可以发现:
- 所有的代码写在一起的,让代码更加简单,易于阅读和维护
- NSThread 通过 @selector 指定要执行的方法,代码分散
- GCD 通过 block 指定要执行的代码,代码集中
- 使用 GCD 不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期
- 如果要开多个线程 NSThread 必须实例化多个线程对象或者使用分类方法
- NSThread 靠 NSObject 的分类方法实现的线程间通信,GCD 靠 block
-
dispatch_async(queue, block);
就是异步执行一个队列里面的任务block。每个block之间是异步执行的,但是block里面的代码是顺序执行的! -
dispatch_sync(queue, block);
就是同步执行一个队列里面的任务block
1. 串行队列(Serial Dispatch Queue)
串行队列的创建:
dispatch_queue_t queue = dispatch_queue_create("queue",DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("queue",NULL);
串行队列同步和异步执行Demo:
//串行队列同步
- (void)serialQueueSyncMethod {
//创建队列
dispatch_queue_t queue = dispatch_queue_create("serialQueueSyncMethod", DISPATCH_QUEUE_SERIAL);
//执行任务
for (int i = 0; i < 6; i++) {
NSLog(@"mainThread--->%d",i);
dispatch_sync(queue, ^{
NSLog(@"Current Thread=%@--->%d----",[NSThread currentThread],i)
});
}
NSLog(@"串行队列同步结束");
}
//串行队列异步执行
- (void)serialQueueAsyncMethod {
dispatch_queue_t queue = dispatch_queue_create("serialQueueAsyncMethod",DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 6; i++) {
NSLog(@"mainThread--->%d",i);
dispatch_async(queue,^{
NSLog(@"Current Thread=%@--->%d----",[NSThread currentThread],i);
});
}
NSLog(@"串行队列异步结束");
}
串行队列,同步执行结果:
2016-11-03 17:16:35.794 ThreadDemo[27088:5268309] mainThread--->0
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->0-----
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] mainThread--->1
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->1-----
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] mainThread--->2
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->2-----
2016-11-03 17:16:35.795 ThreadDemo[27088:5268309] mainThread--->3
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->3-----
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] mainThread--->4
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->4-----
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] mainThread--->5
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] Current Thread=<NSThread: 0x60800007ae40>{number = 1, name = main}---->5-----
2016-11-03 17:16:35.796 ThreadDemo[27088:5268309] 串行队列同步end
串行队列 异步执行结果:
2016-11-03 17:22:25.074 ThreadDemo[27122:5273206] mainThread--->0
2016-11-03 17:22:25.074 ThreadDemo[27122:5273206] mainThread--->1
2016-11-03 17:22:25.074 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->0-----
2016-11-03 17:22:25.074 ThreadDemo[27122:5273206] mainThread--->2
2016-11-03 17:22:25.074 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->1-----
2016-11-03 17:22:25.074 ThreadDemo[27122:5273206] mainThread--->3
2016-11-03 17:22:25.075 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->2-----
2016-11-03 17:22:25.075 ThreadDemo[27122:5273206] mainThread--->4
2016-11-03 17:22:25.075 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->3-----
2016-11-03 17:22:25.075 ThreadDemo[27122:5273206] mainThread--->5
2016-11-03 17:22:25.075 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->4-----
2016-11-03 17:22:25.075 ThreadDemo[27122:5273206] 串行队列异步end
2016-11-03 17:22:25.075 ThreadDemo[27122:5273252] Current Thread=<NSThread: 0x60800007f380>{number = 5, name = (null)}---->5-----
小结:
- 从串行队列同步执行结果看出打印是交替执行的!从打印中看到number=1,说明线程block任务是在主线程中执行的。因为同步是不会开辟线程的,所有当前只有一个主线程MainThread。也就是说串行队列同步执行不会开辟线程,所有block任务之间是同步执行的
- 从串行队列异步执行结果看出打印并不是交替执行的!从打印中看到number=5,说明线程block的任务是在一个全新的线程中执行的。因为异步是会开辟线程的,所有当前有主线程MainThread和子线程number=5。也就是说串行队列异步执行会仅会开辟一个新的线程,所有block任务之间是同步执行的
- 以先进先出的方式,顺序调度队列中的任务执行
- 无论队列中指定的任务函数是同步还是异步,都会等待前一个任务执行完毕以后,再调度后面的任务
2. 并发队列(Concurrent Dispatch Queue)
并发队列创建
dispatch_queue_t queue = dispatch_queue_create("queue",DISPATCH_QUEUE_CONCURRENT);
并发队列的同步和异步执行demo
//并发队列同步
- (void)concurrentQueueSyncMethod {
dispatch_queue_t queue = dispatch_queue_create("concurrentQueueSyncMethod", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 6; i++) {
dispatch_sync(queue, ^{
NSLog(@"Current Thread=%@---->%d----", [NSThread currentThread],i);
});
}
NSLog(@"并发队列同步结束");
}
//并发队列异步执行
- (void)concurrentQueueAsyncMethod {
dispatch_queue_t queue = dispatch_queue_create("concurrentQueueAsyncMethod",DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 6; i++) {
dispatch_async(queue,^{
NSLog(@"Current Thread=%@---->%d----",[NSThread currentThread],i);
});
}
NSLog(@"并发队列异步结束");
}
并发队列同步执行结果:
2016-11-03 17:49:33.850 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->0-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->1-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->2-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->3-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->4-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] Current Thread=<NSThread: 0x60000007bf00>{number = 1, name = main}---->5-----
2016-11-03 17:49:33.851 ThreadDemo[27176:5290096] 并行队列同步end
并发队列异步执行结果:
016-11-03 18:33:32.794 ThreadDemo[27283:5311953] 并行队列异步end
2016-11-03 18:33:32.794 ThreadDemo[27283:5312009] Current Thread=<NSThread: 0x60800026b8c0>{number = 3, name = (null)}---->0-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312006] Current Thread=<NSThread: 0x600000269180>{number = 4, name = (null)}---->1-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312003] Current Thread=<NSThread: 0x600000268f80>{number = 6, name = (null)}---->3-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312174] Current Thread=<NSThread: 0x60800026b740>{number = 8, name = (null)}---->5-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312004] Current Thread=<NSThread: 0x60800026b7c0>{number = 5, name = (null)}---->2-----
2016-11-03 18:33:32.794 ThreadDemo[27283:5312173] Current Thread=<NSThread: 0x60800026b800>{number = 7, name = (null)}---->4-----
小结:
- 并发队列同步执行和串行队列同步执行一样,都不会开辟新线程,block任务之间是同步执行的
- 并发队列异步执行结果中看到开辟了多个线程,并且执行顺序也不是顺序执行。因为异步是多线程的代名词,并发是开多条线程的代名词
- 有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。
- 以先进先出的方式,并发调度队列中的任务执行
- 如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务
- 如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再开辟新的线程调度后续任务的执行
3. 全局队列(Global Dispatch Queue)
全局队列基本知识
dispatch_get_global_queue
函数来获取全局队列是所有应用程序都能够使用的并发队列(Concurrent Dispatch Queue),没必要通过dispatch_queue_create函数逐个生成并发队列,只需要获取Global Dispatch Queue即可
是系统为了方便程序员开发提供的,其工作表现与并发队列一致
-
Global Dispatch Queue有4个优先级,分别是高优先级、默认优先级、低优先级、后台优先级!因为XNU内核用于Global Dispatch Queue的线程并不能保证实时性,因此执行优先级知识大概的判断和区分。
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
全局队列同步和异步执行Demo
//全局队列同步
- (void)globalSyncMethod {
//获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
//执行任务
for (int i = 0; i < 10; i++ ) {
dispatch_sync(queue, ^{
NSLog(@"global_queue_sync%@---->%d----",[NSThread currentThread],i);
});
}
NSLog(@"global_queue_sync_end");
}
//全局队列异步执行
- (void)globalAsyncMethod {
//获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
//执行任务
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"global_queue_async%@---->%d----",[NSThread currentThread],i);
});
}
NSLog(@"globalAsyncMethod end");
}
全局队列 同步执行结果:
2016-11-03 19:06:02.650 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->0----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->1----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->2----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->3----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->4----
2016-11-03 19:06:02.651 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->5----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->6----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->7----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->8----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync<NSThread: 0x600000072200>{number = 1, name = main}---->9----
2016-11-03 19:06:02.652 ThreadDemo[27347:5324064] global_queue_sync_end
全局队列 异步执行结果:
2016-11-03 19:12:00.242 ThreadDemo[27430:5333537] global_queue_async_end
2016-11-03 19:12:00.242 ThreadDemo[27430:5334057] global_queue_async<NSThread: 0x608000266100>{number = 5, name = (null)}---->0----
2016-11-03 19:12:00.242 ThreadDemo[27430:5334053] global_queue_async<NSThread: 0x600000263c80>{number = 6, name = (null)}---->1----
2016-11-03 19:12:00.242 ThreadDemo[27430:5334056] global_queue_async<NSThread: 0x600000263cc0>{number = 7, name = (null)}---->2----
2016-11-03 19:12:00.242 ThreadDemo[27430:5334063] global_queue_async<NSThread: 0x608000268500>{number = 8, name = (null)}---->3----
2016-11-03 19:12:00.242 ThreadDemo[27430:5334064] global_queue_async<NSThread: 0x600000263b80>{number = 9, name = (null)}---->4----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334057] global_queue_async<NSThread: 0x608000266100>{number = 5, name = (null)}---->5----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334053] global_queue_async<NSThread: 0x600000263c80>{number = 6, name = (null)}---->7----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334065] global_queue_async<NSThread: 0x600000073600>{number = 10, name = (null)}---->6----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334056] global_queue_async<NSThread: 0x600000263cc0>{number = 7, name = (null)}---->8----
2016-11-03 19:12:00.243 ThreadDemo[27430:5334066] global_queue_async<NSThread: 0x608000267d40>{number = 11, name = (null)}---->9----
4.主队列 (Main Dispatch Queue)
主队列基本知识
-
dispatch_get_main_queue()
函数来获取 - 专门用来在主线程上调度任务的队列
- 不会开启线程
- 以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行
- 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
主队列同步和异步执行demo
//主队列同步
- (void)mainSyncMethod {
//获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//执行任务
for (int i = 0; i < 10; i++) {
dispatch_sync(queue, ^{
NSLog(@"main_queue_sync%@---->%d----",[NSThread currentThread],i);
});
}
NSLog(@"main_queue_sync_end");
}
//主队列异步
- (void)mainAsyncMethod {
//创建
dispatch_queue_t queue = dispatch_get_main_queue();
//执行
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
NSLog(@"main_queue_async%@--->%ld---",[NSThread currentThread],i);
});
}
NSLog(@"main_queue_async_end");
}
主队列 同步执行结果:
主线程和主队列相互等待造成死锁,程序会直接卡死!
原因:源代码在Main Dispatch Queue 即主队列中执行指定的block任务,并等待其结束。
而其实在主线程中正在执行这些源代码,所以无法执行追加到Main Dispatch Queue 的block任务。
下面例子也一样:
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"main_queue_sync%@----",[NSThread currentThread]);
});
});
主队列 异步执行结果:
2016-11-03 19:45:38.154 ThreadDemo[27501:5349956] main_queue_async_end
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->0----
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->1----
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->2----
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->3----
2016-11-03 19:45:38.155 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->4----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->5----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->6----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->7----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->8----
2016-11-03 19:45:38.156 ThreadDemo[27501:5349956] main_queue_async<NSThread: 0x608000070880>{number = 1, name = main}---->9----
5.延迟执行 (dispatch_after)
- 在GCD中我们使用dispatch_after()函数来延迟执行队列中的任务, dispatch_after()是异步执行队列中的任务的,也就是说使用dispatch_after()来执行队列中的任务不会阻塞当前任务。等到延迟时间到了以后就会开辟一个新的线程然后执行队列中的任务。
-
dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);
来创建
延迟执行(dispatch_after)Demo
- (void)GCDAfterRunMethod {
//循环5次
for (int i = 0; i < 10000;i++) {
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^ {
NSLog(@"This is my %d number!%@",i,[NSThread currentThread]);
});
}
}
注意:
dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);
- 第一个参数:指定时间用的dispatch_time_t类型的值,该值是使用dispatch_time函数或者dispatch_walltime函数生成
-
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
里面有两个参数:参数1获取指定的时间开始,通常为DISPATCH_TIME_NOW,定义: #define DISPATCH_TIME_NOW (0ull)//现在 #define DISPATCH_TIME_FOREVER (~0ull)//永远,意味着你用它你的任务永远不会执行了! 参数2是从参数1延迟指定的时间来执行任务,也是一个时间 #define NSEC_PER_SEC 1000000000ull #define NSEC_PER_MSEC 1000000ull #define USEC_PER_SEC 1000000ull #define NSEC_PER_USEC 1000ull ull是C语言的数值字面量,是显示表明类型是使用的字符串(表示“unsigned long long”) NSEC_PER_SEC 是秒的单位数量级 等价于1亿纳秒 上面代码中提到的(int64_t)(2 * NSEC_PER_SEC))就是2秒了
-
使用dispatch_walltime函数生成dispatch_time_t,用于计算绝对时间,例如在dispatch_after函数中指定2016年11月3日21时45分1秒这一绝对时间,可用作粗略的闹钟功能来使用。也就是到指定时间就会执行任务
//由NSDate类对象获取dispatch_time_t传递给dispatch_after函数使用 dispatch_time_t getDispatchTimeByDate(NSDate *date){ NSTimeInterval interval; double second,subSecond; struct timespec time; dispatch_time_t milestone; interval = [date timeIntervalSince1970]; subSecond = modf(interval, &second); time.tv_sec = second; time.tv_nsec = subSecond * NSEC_PER_SEC; milestone = dispatch_walltime(&time, 0); return milestone; }
-
6. 更改优先级(dispatch_set_target_queue)※
dispatch_queue_create
函数生成的Dispatch Queue不管是 串行队列(Serial Dispatch Queue) 还是 并发队列(Concurrent Dispatch Queue) ,都是用与 默认优先级全局队列(Global Dispatch Queue)相同执行优先级的线程。而全局队列工作方式与并发队列工作方式完全一致!-
dispatch_set_target_queue(dispatch_object_t object,dispatch_queue_t _Nullable queue);函数来更改队列优先级
- 第一个参数:指定要变更优先级的队列(要更改队列)
- 第二个参数:指定第一个参数(队列)要和我们预期队列执行相同优先级的队列(目标队列)
dispatch_set_target_queue第一个Demo:
- (void)GCDSetTargetQueueMethod{
dispatch_queue_t targetQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
//更改优先级
dispatch_set_target_queue(queue1, targetQueue);
for (int i = 0; i < 6; i++) {
dispatch_async(queue1, ^{
NSLog(@"queue1-currentThread = %@-->%d",[NSThread currentThread],i);
});
}
for (int i = 0; i < 6; i++) {
dispatch_async(queue2, ^{
NSLog(@"queue2-----currentThread = %@----->%d",[NSThread currentThread],i);
});
}
}
打印结果:
2016-11-04 11:14:57.034 ThreadDemo[28477:5530919] queue2-----currentThread = <NSThread: 0x60000007e0c0>{number = 11, name = (null)}----->1
2016-11-04 11:14:57.034 ThreadDemo[28477:5530920] queue2-----currentThread = <NSThread: 0x60000007a140>{number = 12, name = (null)}----->2
2016-11-04 11:14:57.034 ThreadDemo[28477:5530918] queue2-----currentThread = <NSThread: 0x60000007a100>{number = 10, name = (null)}----->0
2016-11-04 11:14:57.034 ThreadDemo[28477:5530919] queue2-----currentThread = <NSThread: 0x60000007e0c0>{number = 11, name = (null)}----->3
2016-11-04 11:14:57.034 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->0
2016-11-04 11:14:57.034 ThreadDemo[28477:5530920] queue2-----currentThread = <NSThread: 0x60000007a140>{number = 12, name = (null)}----->5
2016-11-04 11:14:57.034 ThreadDemo[28477:5530921] queue2-----currentThread = <NSThread: 0x60000007c340>{number = 13, name = (null)}----->4
2016-11-04 11:14:57.035 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->1
2016-11-04 11:14:57.036 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->2
2016-11-04 11:14:57.036 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->3
2016-11-04 11:14:57.036 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->4
2016-11-04 11:14:57.037 ThreadDemo[28477:5530912] queue1-currentThread = <NSThread: 0x60000007ec00>{number = 9, name = (null)}-->5
打印解释:
- queue1和queue2设置的目标队列是全局队列(并发),也就是允许要更改的队列可以开辟多条线程(可以超过一条)
- 代码中queue1变更和低优先级的全局队列一样优先级的串行队列,queue2是dispatch_queue_create创建和默认优先级的全局队列一样优先级的并发队列
- queue1和queue2都是异步执行,都会开辟线程,queue2是并发队列所以打印中看到有多个线程,并且任务之间也不是顺序执行。queue1是串行队列,所以只会开辟一个线程,任务会顺序执行。
- queue2队列的优先级比queue1队列的优先级要高,从打印中可以看到queue2的确比queue1任务要普遍先执行。为什么要说普遍呢,因为从结果上看到queue1有一个任务是在queue2里任务没有执行完毕也执行了,为什么会出现这个问题呢?
在上面全局队列中说到了优先级的事情
因为XNU内核用于Global Dispatch Queue的线程并不能保证实时性,因此执行优先级知识大概的判断和区分,所有我们不能完全依赖这个优先级来做队列的顺序事情,否则会出现问题!切记
dispatch_set_target_queue第二个Demo:
- dispatch_set_target_queue除了能用来设置队列的优先级之外,还能够创建队列的执行层次,当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的target指向新创建的队列即可,比如:
- (void)GCDSetTargetQueueMethod{
dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);//目标队列
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行队列
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);//并发队列
//设置参考
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
for (int i = 0; i < 6; i++) {
dispatch_async(queue1, ^{
NSLog(@"queue1-currentThread = %@-->%d",[NSThread currentThread],i);
});
}
for (int i = 0; i < 6; i++) {
dispatch_async(queue2, ^{
NSLog(@"queue2-currentThread = %@-->%d",[NSThread currentThread],i);
});
}
}
打印结果:
2016-11-04 11:34:34.722 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->0
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->1
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->2
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->3
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->4
2016-11-04 11:34:34.723 ThreadDemo[28551:5540844] queue1-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->5
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->0
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->1
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->2
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->3
2016-11-04 11:34:34.724 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->4
2016-11-04 11:34:34.725 ThreadDemo[28551:5540844] queue2-currentThread = <NSThread: 0x60000007b640>{number = 3, name = (null)}-->5
打印解释:
- queue1和queue2设置的目标队列是串行队列,也就是允许要更改的队列可以开辟一条线程。
- queue1和queue2优先级一样,他两执行顺序相当于一个串行队列异步执行!
- queue1和queue2队列以targetQueue队列为参照对象,那么queue1和queue2中的任务将按照targetQueue的队列处理。
- 适用场景:一般都是把一个任务放到一个串行的queue中,如果这个任务被拆分了,被放置到多个串行的queue中,但实际还是需要这个任务同步执行,那么就会有问题,因为多个串行queue之间是并行的。这时候dispatch_set_target_queue将起到作用。
dispatch_set_target_queue小结:
- dispatch_set_target_queue可以更改Dispatch Queue优先级。
- dispatch_set_target_queue可以更改队列的执行层次,队列里的任务将会按照目标队列(target Queue)的队列来处理
7. 任务组Dispatch Group※
GCD的任务组在开发中是经常被使用到,==当你一组任务结束后再执行一些操作时,使用任务组在合适不过了
==。dispatch_group的职责就是当队列中的所有任务都执行完毕后在去做一些操作,也就是说在任务组中执行的队列,当队列中的所有任务都执行完毕后就会发出一个通知来告诉用户任务组中所执行的队列中的任务执行完毕了。关于将队列放到任务组中执行有两种方式:
- 一种是使用
dispatch_group_async()
函数,将队列与任务组进行关联并自动执行队列中的任务 - 另一种方式是手动的将队列与组进行关联然后使用异步将队列进行执行,也就是d
ispatch_group_enter()
与dispatch_group_leave()
方法的使用。下方就给出详细的介绍。
1.队列与组自动关联并执行
首先我们来介绍dispatch_group_async()
函数的使用方式,该函数会将队列与相应的任务组进行关联,并且自动执行。当与任务组关联的队列中的任务都执行完毕后,会通过dispatch_group_notify()
函数发出通知告诉用户任务组中的所有任务都执行完毕了。==使用通知的方式是不会阻塞当前线程的
==,如果你==使用dispatch_group_wait()函数,那么就会阻塞当前线程
==,直到任务组中的所有任务都执行完毕。
//自动执行任务组
- (void)GCDAutoDispatchGroupMethod {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i<6; i++) {
dispatch_group_async(group,queue, ^{
NSLog(@"current Thread = %@----->%d",[NSThread currentThread],i);
});
}
dispatch_group_notify(group,dispatch_get_main_queue(), ^{
NSLog(@"current Thread = %@----->这是最后执行",[NSThread currentThread]);
});
}
打印结果:
2016-11-04 15:11:34.010 ThreadDemo[29225:5641180] current Thread = <NSThread: 0x600000468200>{number = 5, name = (null)}----->2
2016-11-04 15:11:34.010 ThreadDemo[29225:5641193] current Thread = <NSThread: 0x60800007f900>{number = 3, name = (null)}----->0
2016-11-04 15:11:34.011 ThreadDemo[29225:5641438] current Thread = <NSThread: 0x608000467980>{number = 8, name = (null)}----->5
2016-11-04 15:11:34.010 ThreadDemo[29225:5641178] current Thread = <NSThread: 0x608000467640>{number = 6, name = (null)}----->3
2016-11-04 15:11:34.010 ThreadDemo[29225:5641177] current Thread = <NSThread: 0x6080004675c0>{number = 4, name = (null)}----->1
2016-11-04 15:11:34.010 ThreadDemo[29225:5641437] current Thread = <NSThread: 0x600000467f40>{number = 7, name = (null)}----->4
2016-11-04 15:11:34.011 ThreadDemo[29225:5641137] current Thread = <NSThread: 0x60800007e200>{number = 1, name = main}----->这是最后执行
上面的函数就是使用
dispatch_group_async()
函数将队列与任务组进行关联并执行。首先我们创建了一个全局队列(并发),然后又创建了一个类型为dispatch_group_t
的任务组group。使用dispatch_group_async()
函数将两者进行关联并执行。使用dispatch_group_notify()
函数进行监听group中队列的执行结果,如果执行完毕后,我们就在主线程中对结果进行处理。dispatch_group_notify()
函数有两个参数一个是发送通知的group,另一个是处理返回结果的队列。dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
函数与dispatch_async函数相同,都追加block到指定的Dispatch Queue中,与dispatch_async不同的是==指定生成的Dispatch Group为第一个参数。指定的block属于指定的Dispatch Group,不是Dispatch Queue,切记!==无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可监视这些处理执行的结束。一旦检测到所有处理执行结束,就可将结束的处理追加到Dispatch Queue中。这就是使用Dispatch Group的原因。
在追加到Dispatch Group中的处理全部执行结束时,代码中使用的dispatch_group_notify函数会将执行的Block追加到Dispatch Queue中,将第一个参数指定为要监视的Dispatch Group。在追加到该Dispatch Group的全部处理执行结束时,将第三个参数的Block追加到第二个参数的Dispatch Queue中。在dispatch_group_notify函数中不管指定什么样的Dispatch Queue,属于Dispatch Group的全部处理在追加指定的Block时都已执行结束。
-
另外,在Dispatch Group中也可以使用dispatch_group_wait函数仅等待全部处理执行结束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{NSLog(@"blk0");}); dispatch_group_async(group, queue, ^{NSLog(@"blk1");}); dispatch_group_async(group, queue, ^{NSLog(@"blk2");}); dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
-
dispatch_group_wait函数的第二个参数指定为等待时间(超时)。它属于dispatch_time_t类型的值。代码中使用的DISPATCH_TIME_FOREVER,意味着永久等待。只要属于Dispatch Group的操作尚未执行结束,就会一直等待,中途不能取消。
指定等待时间为1微秒时,应做如下处理:
//等待group处理结束 //dispatch_group_wait(group, DISPATCH_TIME_FOREVER); dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1*USEC_PER_SEC));//1微秒 long result = dispatch_group_wait(group, time); if (result == 0) { //属于Dispatch Group 的block任务全部处理结束 NSLog(@"Dispatch Group全部处理完毕"); }else{ //属于Dispatch Group 的block任务还在处理中 NSLog(@"Dispatch Group正在处理"); }
如果dispatch_group_wait函数的返回值不为0,就意味着虽然经过了指定的时间,但属于Dispatch Group的某一个处理还在执行中。如果返回值为0,那么全部处理执行结束。当等待时间为DISPATCH_TIME_FOREVER,由dispatch_group_wait函数返回时,属于Dispatch Group的处理必定全部执行结束,因此返回值恒为0。
这里的“等待”是什么意思?这意味着一旦调用dispatch_group_wait函数,该函数就处于调用的状态而不返回。即执行dispatch_group_wait函数的现在的线程(当前线程)停止。在经过dispatch_group_wait函数中指定的时间或属于指定Dispatch Group的处理全部执行结束之前,执行该函数的线程停止,属于阻塞状态。
-
指定DISPACH_TIME_NOW,则不用任何等待即可判定属于Dispatch Group的处理是否执行结束。
long result = diaptach_group_wait(group, DISPACH_TIME_NOW);
在主线程的RunLoop的每次循环中,可检查执行是否结束,从而不消耗多余的等待时间,虽然这样有额可以,但一般在这种情况下,还是推荐用dispatch_group_notify函数追加结束处理到Main Dispatch Queue中。这是因为dispatch_group_notify函数可以简化代码。并且如果你用了diaptach_group_wait等待时间过长,中间不能取消队列任务这就很坑了!
2.队列与组手动关联并执行
- 接下来我们将手动的管理任务组与队列中的关系,也就是不使用dispatch_group_async()函数。我们使用dispatch_group_enter()与dispatch_group_leave()函数将队列中的每次任务加入到到任务组中。首先我们使用dispatch_group_enter()函数进入到任务组中,然后异步执行队列中的任务,最后使用dispatch_group_leave()函数离开任务组即可。下面的函数中我们使用了dispatch_group_wait()函数,该函数的职责就是阻塞当前线程,来等待任务组中的任务执行完毕。该函数的第一个参数是所要等待的group,第二个参数是等待超时时间,此处我们设置的是DISPATCH_TIME_FOREVER,就说明等待任务组的执行永不超时,直到任务组中所有任务执行完毕。
//手动执行任务组
- (void)GCDManualDispatchGroupMethod{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 6; i++) {
dispatch_group_enter(group);//进入队列组
dispatch_async(queue, ^{
NSLog(@"current Thread = %@----->%d",[NSThread currentThread],i);
dispatch_group_leave(group);//离开队列组
});
}
long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//阻塞当前线程,直到所有任务执行完毕才会继续往下执行
if (result == 0) {
//属于Dispatch Group 的block任务全部处理结束
NSLog(@"Dispatch Group全部处理完毕");
}else{
//属于Dispatch Group 的block任务还在处理中
NSLog(@"Dispatch Group正在处理");
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"current Thread = %@----->这是最后执行",[NSThread currentThread]);
});
}
打印结果: dispatch_group_wait()函数下方的print()函数在所有任务执行完毕之前是不会被调用的,因为dispatch_group_wait()会将当前线程进行阻塞。当然虽然是手动的将队列与任务组进行关联的,感觉display_group_notify()函数还是好用的。
2016-11-04 16:19:13.802 ThreadDemo[29402:5678525] current Thread = <NSThread: 0x600000271780>{number = 5, name = (null)}----->2
2016-11-04 16:19:13.802 ThreadDemo[29402:5678527] current Thread = <NSThread: 0x608000077300>{number = 3, name = (null)}----->0
2016-11-04 16:19:13.802 ThreadDemo[29402:5678524] current Thread = <NSThread: 0x60800007d840>{number = 4, name = (null)}----->1
2016-11-04 16:19:13.803 ThreadDemo[29402:5678760] current Thread = <NSThread: 0x600000271cc0>{number = 8, name = (null)}----->5
2016-11-04 16:19:13.802 ThreadDemo[29402:5678545] current Thread = <NSThread: 0x60800007d000>{number = 6, name = (null)}----->3
2016-11-04 16:19:13.802 ThreadDemo[29402:5678544] current Thread = <NSThread: 0x60800007d040>{number = 7, name = (null)}----->4
2016-11-04 16:19:13.803 ThreadDemo[29402:5678489] Dispatch Group全部处理完毕
2016-11-04 16:19:13.804 ThreadDemo[29402:5678489] current Thread = <NSThread: 0x600000069dc0>{number = 1, name = main}----->这是最后执行
9.栅栏任务Dispatch_barrier_async
- barrier顾名思义栅栏、障碍物的意思!在访问数据库或文件时,使用Serial Dispatch Queue可避免数据竞争的问题。
- 虽然利用Dispatch Group和dispatch_set_target_queue函数也可实现,但代码会很复杂。有兴趣的可以自己尝试写写!GCD为我们提供了更为聪明的解决办法——dispatch_barrier_async函数。该函数同dispatch_queue_create函数生成的Concurrent Dispatch Queue一起使用
#pragma mark - Dispatch_barrier_async
- (void)GCDBarrierAsyncMethod{
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
void(^blk1_reading)(void) = ^{
NSLog(@"blk1---reading");
};
void(^blk2_reading)(void) = ^{
NSLog(@"blk2---reading");
};
void(^blk3_reading)(void) = ^{
NSLog(@"blk3---reading");
};
void(^blk4_reading)(void) = ^{
NSLog(@"blk4---reading");
};
void(^blk_writing)(void) = ^{
NSLog(@"blk---writing");
};
dispatch_async(concurrentQueue, blk1_reading);
dispatch_async(concurrentQueue, blk2_reading);
//添加追加操作,,会等待b1和b2全部执行结束,执行完成追加操作b,才会继续并发执行下面操作
dispatch_barrier_async(concurrentQueue, blk_writing);
dispatch_async(concurrentQueue, blk3_reading);
dispatch_async(concurrentQueue, blk4_reading);
}
打印结果:
2016-11-04 17:02:13.202 ThreadDemo[29492:5700974] blk2---reading
2016-11-04 17:02:13.202 ThreadDemo[29492:5700972] blk1---reading
2016-11-04 17:02:13.203 ThreadDemo[29492:5700972] blk---writing
2016-11-04 17:02:13.203 ThreadDemo[29492:5700972] blk3---reading
2016-11-04 17:02:13.203 ThreadDemo[29492:5700974] blk4---reading
使用Concurrent Dispatch Queue和dispatch_barrier_async函数可实现高效率的数据库访问和文件访问,dispatch_barrier_async函数还是比较好理解的。
9. 循环执行dispatch_apply
- dispatch_apply()函数是用来循环来执行队列中的任务的,使用方式为:dispatch_apply(循环次数, 任务所在的队列) { 要循环执行的任务 }。使用该函数循环执行并行队列中的任务时,会开辟新的线程,不过有可能会在当前线程中执行一些任务。而使用dispatch_apply()执行串行队列中的任务时,会在当前线程中执行。无论是使用并行队列还是串行队列,dispatch_apply()都会阻塞当前执行函数线程。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index){
NSLog(@"%zu", index);
});
NSLog(@"done");
2016-11-04 17:24:25.819 ThreadDemo[29598:5715955] 2
2016-11-04 17:24:25.819 ThreadDemo[29598:5715904] 0
2016-11-04 17:24:25.819 ThreadDemo[29598:5716588] 1
2016-11-04 17:24:25.819 ThreadDemo[29598:5716640] 3
2016-11-04 17:24:25.820 ThreadDemo[29598:5715955] 4
2016-11-04 17:24:25.820 ThreadDemo[29598:5715904] 5
2016-11-04 17:24:25.820 ThreadDemo[29598:5716588] 6
2016-11-04 17:24:25.820 ThreadDemo[29598:5716640] 7
2016-11-04 17:24:25.820 ThreadDemo[29598:5715904] 9
2016-11-04 17:24:25.820 ThreadDemo[29598:5715955] 8
2016-11-04 17:24:25.820 ThreadDemo[29598:5715904] done
- 输出结果中最后的done必定在最后的位置上,这是因为dispatch_apply函数会等待全部处理执行结束,也就是阻塞当前执行函数线程。
- 另外,由于dispatch_apply函数也与dispatch_sync函数相同,会等待处理执行结束(阻塞),因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。
#pragma mark - Dispatch_apply
- (void)GCDDispatchApplyMethod{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
void(^blk1_reading)(void) = ^{
NSLog(@"blk1---reading");
};
void(^blk2_reading)(void) = ^{
NSLog(@"blk2---reading");
};
void(^blk3_reading)(void) = ^{
NSLog(@"blk3---reading");
};
void(^blk_writing)(void) = ^{
NSLog(@"blk---writing");
};
NSMutableArray *array = [NSMutableArray new];
[array addObject:blk1_reading];
[array addObject:blk2_reading];
[array addObject:blk3_reading];
[array addObject:blk_writing];
dispatch_async(queue, ^{
dispatch_apply(array.count, queue, ^(size_t index) {
void (^blk)(void) = [array objectAtIndex:index];
blk();
NSLog(@"%zu====%@",index,[array objectAtIndex:index]);
});
NSLog(@"全部执行结束");
dispatch_async(dispatch_get_main_queue(), ^{
//在main Dispatch queue中执行处理,更新用户界面等待
NSLog(@"done");
});
});
}
打印结果:
2016-11-04 17:36:02.258 ThreadDemo[29635:5723967] blk3---reading
2016-11-04 17:36:02.258 ThreadDemo[29635:5723940] blk2---reading
2016-11-04 17:36:02.258 ThreadDemo[29635:5723929] blk1---reading
2016-11-04 17:36:02.258 ThreadDemo[29635:5723968] blk---writing
2016-11-04 17:36:02.259 ThreadDemo[29635:5723940] 1====<__NSGlobalBlock__: 0x102895af0>
2016-11-04 17:36:02.259 ThreadDemo[29635:5723967] 2====<__NSGlobalBlock__: 0x102895b30>
2016-11-04 17:36:02.259 ThreadDemo[29635:5723929] 0====<__NSGlobalBlock__: 0x102895ab0>
2016-11-04 17:36:02.259 ThreadDemo[29635:5723968] 3====<__NSGlobalBlock__: 0x102895b70>
2016-11-04 17:36:02.259 ThreadDemo[29635:5723940] 全部执行结束
2016-11-04 17:36:02.259 ThreadDemo[29635:5723499] done
10. 队列的挂起和唤醒
- 队列的挂起与唤醒相对较为简单,如果你想对一个队列中的任务的执行进行挂起,那么你就使用dispatch_suspend()函数即可。如果你要唤醒某个挂起的队列,那么你就可以使用dispatch_resum()函数。这两个函数所需的参数都是你要挂起或者唤醒的队列,鉴于知识点的简单性就不做过多的赘述了。
#pragma mark -Dispatch_suspend/Dispatch_resume
- (void)GCDDispatch_suspend_resume{
//系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。
dispatch_queue_t queue1 = dispatch_queue_create("queue1", 0);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", 0);
dispatch_group_t group = dispatch_group_create();
dispatch_async(queue1, ^{
for (int i = 0; i < 5; i++) {
NSLog(@"%@-------%d",[NSThread currentThread],i);
sleep(1);
}
});
dispatch_async(queue2, ^{
NSLog(@"task2");
});
dispatch_group_async(group, queue1, ^{
NSLog(@"task1 finished!");
});
dispatch_group_async(group, queue2, ^{
dispatch_suspend(queue1);//挂起
NSLog(@"task2 finished!挂起queue1");
[NSThread sleepForTimeInterval:20.0];
dispatch_resume(queue1);//唤醒队列
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(queue1, ^{
NSLog(@"task3");
});
dispatch_async(queue2, ^{
NSLog(@"task4");
});
}
打印结果:可以先思考一下打印结果
2016-11-04 17:59:41.219 ThreadDemo[29749:5738141] task2
2016-11-04 17:59:41.219 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------0
2016-11-04 17:59:41.220 ThreadDemo[29749:5738141] task2 finished!挂起queue1
2016-11-04 17:59:42.220 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------1
2016-11-04 17:59:43.223 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------2
2016-11-04 17:59:44.225 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------3
2016-11-04 17:59:45.230 ThreadDemo[29749:5738138] <NSThread: 0x60000027c380>{number = 4, name = (null)}-------4
2016-11-04 18:00:01.220 ThreadDemo[29749:5738141] task1 finished!
2016-11-04 18:00:01.220 ThreadDemo[29749:5738141] task3
2016-11-04 18:00:01.221 ThreadDemo[29749:5738769] task4
11. 信号量Dispatch Semaphore
当并行执行的处理更新数据时,会产出数据不一致的情况,有时应用就会异常退出。可以使用Serial Dispatch Queue 和 dispatch_barrier也可以处理这种情况,我们也可以使用信号量Dispatch Semaphor来更好的处理线程安全和同步的问题。
Dispatch Semaphore是持有计数的信号。使用计数来实现该等待还是运行功能。计数为0时等待,计数为1或者大于1时,减去1而不是等待,就可以运行。
-
其实信号量就是根据pv操作来实现的
p操作-1 v操作+1 信号量就是一个资源计数器,对信号量有两个操作来达到互斥,分别是P和V操作。
一般情况是这样进行临界访问或互斥访问的: 设信号量值为1, 当一个进程1运行时,使用资源,进行P操作,即对信号量值减1,也就是资源数少了1个。这是信号量值为0。系统中规定当信号量值为0是,必须等待,直到信号量值不为零才能继续操作。 这时如果进程2想要运行,那么也必须进行P操作,但是此时信号量为0,所以无法减1,即不能P操作,也就阻塞。这样就到到了进程1排他访问。 当进程1运行结束后,释放资源,进行V操作。资源数重新加1,这是信号量的值变为1. 这时进程2发现资源数不为0,信号量能进行P操作了,立即执行P操作。信号量值又变为0.次数进程2咱有资源,排他访问资源。 这就是信号量来控制互斥的原理
信号量为0则阻塞线程,大于0则不会阻塞。则我们通过改变信号量的值,来控制是否阻塞线程,从而达到线程同步。
- (void)GCDDispatchSemaphore{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建一个信号量dispatch
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArray new];
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
//一直等待信号量大于0
dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, DISPATCH_TIME_FOREVER));
//进行排他控制的处理代码...
//将dispatch semphore的计数器减1
[array addObject:[NSNumber numberWithInteger:i]];
//排他控制处理结束
//将dispatch semphore的计数器加1
//如果有通过dispatch_semaphore_wait函数dispatch semphore的计数值增加的线程,等待就由最先等待的线程执行
dispatch_semaphore_signal(semaphore);
});
NSLog(@"%@",array);
}
}
创建一个信号量dispatch dispatch_semaphore_t dispatch_semaphore_create(long value);
等待信号量longdispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);返回值和dispatch_group_wait函数相同,也可以通过下面进行分支处理:
long result = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10*NSEC_PER_SEC)));
if (result == 0){
//进行排他控制的处理代码...
//将dispatch semphore的计数器减1
[array addObject:[NSNumber numberWithInteger:i]];
//排他控制处理结束
//将dispatch semphore的计数器加1
//如果有通过dispatch_semaphore_wait函数dispatch semphore的计数值增加的线程,等待就由最先等待的线程执行
dispatch_semaphore_signal(semaphore);
}else{
//这个时候计数值为0,在达到指定时间为止还是等待状态。
//处理逻辑....
}
在进行排他处理结束时,通过dispatch_semaphore_signal来讲计数值加1
GCD总结
- 开不开线程由执行任务的函数决定
- 异步开,异步是多线程的代名词
- 同步不开
- 开几条线程由队列决定
- 串行队列开一条线程(GCD会开一条,NSOperation Queue最大并发数为1时也可能开多条)
- 并发队列开多条线程,具体能开的线程数量由底层线程池决定
GCD的使用步骤
- 创建block任务
- 创建GCD队列(串行和并发)
- 把block任务放到GCD队列里,以异步或者同步方式执行GCD队列
iOS编程技术三-NSOperation
一. NSOperation基本介绍
1.NSOperation与GCD
NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高一层的封装,但是比GCD更简单易用、代码可读性也更高,下面是NSOperation和GCD的优缺点:
-
GCD的优缺点:
- 是基于C语言实现的,一种更轻量级的,以FIFO的方式的顺序执行并发任务
- 使用GCD时我们并不关心任务的调度情况,而让系统帮我们自动处理
-
GCD的缺点:
- 给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手
-
NSOperation的优点:
- 可以给operation之间添加依赖关系、取消一个正在执行的operation、暂停和恢复operation queue等,灵活性非常强大
-
NSOperation的缺点:
- 相对GCD而言会增加一点开销
2.NSOperation的使用步骤
因为NSOperation是基于GCD的,那么使用起来也和GCD差不多,其中,NSOperation相当于GCD中的任务,而NSOperationQueue则相当于GCD中的队列。NSOperation实现多线程的使用步骤分为三步:
- 创建任务:先将需要执行的操作封装到一个NSOperation对象中
- 创建队列:创建NSOperationQueue对象
- 将任务加入到队列中:然后将NSOperation对象添加到NSOperationQueue中
之后,系统就会自动将NSOperationQueue中的NSOperation取出来,在新线程中执行操作。
二. NSOperation和NSOperationQueue的基本使用
创建任务
- 在默认情况下,NSOperation 是同步执行的,也就是说会阻塞当前线程直到任务完成。
- NSOperation 本身是一个抽象类,不能直接实例化,因此,如果我们想要使用它来执行具体任务的话,就必须使用系统预定义的两个子类NSInvocationOperation和NSBlockOperation或者创建自己的子类。所以下面介绍3种NSOPeration实例的创建:
- 1.使用子类NSInvocationOperation
- 2.使用子类NSBlockOperation
- 3.定义继承自NSOperation的子类,通过实现内部相应的方法来创建任务
1. 使用子类NSInvocationOperation
NSInvocationOperation:我们可以通过一个 object 和 selector 非常方便地创建一个NSInvocationOperation,这是一种非常动态和灵活的方式。
创建方法:
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)arg;
使用demo:
- (void)invocationOperation {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
op.completionBlock = ^ {
NSLog(@"任务完成后回调block");
};
[op start];
}
- (void)run {
NSLog(@"----%@",[NSThread currentThread]);
}
结果:
2018-07-13 11:28:20.4 MultipleThreadDemo[2405:1751612] ----<NSThread: 0x1c4070600>{number = 1, name = main}
2018-07-13 11:28:20.4 MultipleThreadDemo[2405:1751723] 任务完成后回调block
2. 使用子类NSBlockOperation
NSBlockOperation:我们可以使用 NSBlockOperation 来并发执行一个或多个 block ,只有当一个 NSBlockOperation 所关联的所有 block 都执行完毕时(会阻塞当前线程),这个 NSBlockOperation 才算执行完成,有点类似于 dispatch_group 的概念。
方法:
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
- (void)addExecutionBlock:(void (^)(void))block;
Demo:
- (void)blockOperationAddTask {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^ {
NSLog(@"I'm running---%@",[NSThread currentThread].description);
}];
for (int i = 0; i < 20; i++) {
[op addExecutionBlock:^{
NSLog(@"added block running---%@",[NSThread currentThread].description);
}];
}
NSLog(@"op start");
[op start];
NSLog(@"op end");
}
打印的结果:
op start
I'm running---<NSThread: 0x1c0063fc0>{number = 1, name = main}
added block running---<NSThread: 0x1c0473800>{number = 4, name = (null)}
added block running---<NSThread: 0x1c0063fc0>{number = 1, name = main}
added block running---<NSThread: 0x1c0473800>{number = 4, name = (null)}
added block running---<NSThread: 0x1c0063fc0>{number = 1, name = main}
added block running---<NSThread: 0x1c0473800>{number = 4, name = (null)}
added block running---<NSThread: 0x1c0063fc0>{number = 1, name = main}
added block running---<NSThread: 0x1c0473800>{number = 4, name = (null)}
op end
NSBlockOperation注意点:
从上面打印结果看到在多个线程执行任务。addExecutionBlock:
可以为NSBlockOperation添加额外的操作。如果当前NSOperation的任务只有一个的话,那肯定不会开辟一个新的线程,只能同步执行。只有NSOperation的任务数>1的时候,这些额外的操作才有可能在其他线程并发执行。注意我的用词 "才有可能",也就是说额外的操作也有可能在当前线程里执行。
3. 使用自定义的NSOperation的子类
NSOperation的子类:当系统预定义的两个子类 NSInvocationOperation 和 NSBlockOperation 不能很好的满足我们的需求时,我们可以自定义自己的 NSOperation 子类,添加我们想要的功能。我们可以自定义非并发和并发两种不同类型的 NSOperation 子类,而自定义一个前者要比后者简单得多
非并发的NSOperation子类Demo:
先定义一个继承自NSOperation的子类,重写main方法
SerialOperation.h
#import <Foundation/Foundation.h>
typedef void(^CompletionBlock)(NSDate* imageData);
@interface SerialOperation : NSOperation
@property (nonatomic, copy) CompletionBlock comBlock;
@end
SerialOperation.m
#import "SerialOperation.h"
@implementation SerialOperation
- (void)main {
@autoreleasepool {
if (self.isCancelled) {
return;
}
NSURL *url = [NSURL URLWithString:@""];
NSData * imageData = [NSData dataWithContentsOfURL:url];
if (!imageData) {
imageData = nil;
}
if (self.isCancelled) return;
[self performSelectorOnMainThread:@selector(completionAction:) withObject:imageData];
}
}
@end
调用:
//在主线程中执行并没有开辟线程
- (void)serialOperation {
SerialOperation * op = [[SerialOperation alloc] init];
op.comBlock = ^(NSData* imageData) {
self.imageView.image = [UIImage imageWithData:imageData];
};
[op start];
}
三. 创建队列
和GCD中的并发队列、串行队列略有不同的是:NSOperationQueue一共有两种队列:主队列、非主队列。其中非主队列==同时包含了串行、并发功能==。下边是主队列、其他队列的基本创建方法和特点。
主队列
凡是添加到主队列中的任务(NSOperation),都会放到主线程中执行
NSOperationQueue *queue = [NSOperationQueue mainQueue];
非主队列
添加到这种队列中的任务(NSOperation),就会自动放到子线程中执行
-
同时包含了:串行、并发功能
NSOperationQueue* queue = [[NSOperationQueue alloc]init];
四. 把任务加入到队列中
只要将任务加入到队列中,就不要执行start
方法,队列会负责调度任务自动执行start
方法。加入队列的方法如下:
- (void)addOperation:(NSOperation *)op;//添加单个任务
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;//添加任务数组
- (void)addOperationWitBlock:(void(^)(void))block;//添加单个任务
*1. - (void)addOperation:(NSOperation )op;
首先创建任务operation,然后将创建好的任务添加队列!
//多任务的operation
- (void)addOperationToQueue {
// 1.创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建操作
// 创建NSInvocationOperation
NSInvocationOperation * op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run2) object:nil];
// 创建NSBlockOperation
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"op2--->%d----%@",i,[NSThread currentThread]);
}
}];
// 3.添加操作到队列中:addOperation:
[queue addOperation:op1];//[op1 start];
[queue addOperation:op2];//[op2 start];
}
- (void)run2 {
for (int i=0; i < 5; i++) {
NSLog(@"op1--->%d----%@",i,[NSThread currentThread]);
}
}
打印结果:
op1--->0----<NSThread: 0x1c047f240>{number = 9, name = (null)}
op1--->1----<NSThread: 0x1c047f240>{number = 9, name = (null)}
op2--->0----<NSThread: 0x1c4274540>{number = 10, name = (null)}
op2--->1----<NSThread: 0x1c4274540>{number = 10, name = (null)}
op2--->2----<NSThread: 0x1c4274540>{number = 10, name = (null)}
op2--->3----<NSThread: 0x1c4274540>{number = 10, name = (null)}
op2--->4----<NSThread: 0x1c4274540>{number = 10, name = (null)}
op1--->2----<NSThread: 0x1c047f240>{number = 9, name = (null)}
op1--->3----<NSThread: 0x1c047f240>{number = 9, name = (null)}
op1--->4----<NSThread: 0x1c047f240>{number = 9, name = (null)}
从上面可以看到NSOperation Queue会开辟线程。然后并发执行!
2. - (void)addOperationWithBlock:(void (^ )(void))block;
无需先创建任务,在block中添加任务,直接将任务block加入到队列中
- (void)addOperationWithBlockToQueue {
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
// 设置最大并发操作数
// queue.maxConcurrentOperationCount = 1;//就变成了串行队列
queue.maxConcurrentOperationCount = 5;
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{
NSLog(@"%d---%@",i,[NSThread currentThread]);
}];
}
}
结果:
2016-11-07 22:13:14.189 ThreadDemo[2933:60785] 2-----<NSThread: 0x600000274840>{number = 10, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60814] 0-----<NSThread: 0x608000260f00>{number = 14, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60803] 1-----<NSThread: 0x600000275600>{number = 11, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60812] 4-----<NSThread: 0x600000274a40>{number = 13, name = (null)}
2016-11-07 22:13:14.189 ThreadDemo[2933:60695] 3-----<NSThread: 0x608000260a00>{number = 9, name = (null)}
可以看出addOperationWithBlock:和NSOperationQueue能够开启新线程,进行并发执行。
3. maxConcurrentOperationCount
-
maxConcurrentOperationCount
是队列的最大并发数,也就是当前执行队列的任务时,最多开辟多少条线程!具体开多少条线程是由底层线程池来决定 - 队列是串行还是并发就是由maxConcurrentOperationCount来决定
- axConcurrentOperationCount默认情况下为-1,表示不进行限制,默认为并发执行。
- 当maxConcurrentOperationCount为1时,进行串行执行
- 当maxConcurrentOperationCount大于1时,进行并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整
代码参考上一个- (void)addOperationWithBlock:(void (^)(void))block ;Demo,修改最大并发数即可测试,结果如下:
//maxConcurrentOperationCount=1
2016-11-08 10:05:34.748 ThreadDemo[1224:14753] 0-----<NSThread: 0x600000263100>{number = 4, name = (null)}
2016-11-08 10:05:34.749 ThreadDemo[1224:14753] 1-----<NSThread: 0x600000263100>{number = 4, name = (null)}
2016-11-08 10:05:34.749 ThreadDemo[1224:14753] 2-----<NSThread: 0x600000263100>{number = 4, name = (null)}
2016-11-08 10:05:34.750 ThreadDemo[1224:14754] 3-----<NSThread: 0x6000002635c0>{number = 3, name = (null)}
2016-11-08 10:05:34.750 ThreadDemo[1224:14754] 4-----<NSThread: 0x6000002635c0>{number = 3, name = (null)}
注意点:
- maxConcurrentOperationCount设置为1时,是串行队列,也有可能开辟多条线程。串行只是一种执行任务的方式,跟开辟线程是不同纬度的概念别弄混了,同步和异步决定开不开线程
- maxConcurrentOperationCount设置为1时,是串行队列,但是 operation 的执行顺序还是一样会受其他因素影响的,比如 operation 的 isReady 状态、operation 的队列优先级等,如果operation 的执行顺序对我们来说非常重要,那么我们就应该在将 operation 添加到 operation queue 之前就建立好它的依赖关系。
五. 任务的操作依赖
通过配置依赖关系,我们可以让不同的 operation 串行执行,正如我们上面刚刚提到的最大并发数为1时串行执行(但是顺序不一定会是我们想要的顺序),一个 operation 只有在它依赖的所有 operation 都执行完成后才能开始执行。配置 operation 的依赖关系主要涉及到NSOperation 类中的以下两个方法:
- (void)addDependency:(NSOperation *)op;//添加依赖
- (void)removeDependency:(NSOperation *)op;//删除依赖
-
特别注意1:
addDependency
:方法添加的依赖关系是单向的,比如 [A addDependency:B];,表示 A 依赖 B,B 并不依赖 A - 特别注意2:addDependency:可以跨队列添加依赖,原因:上面添加依赖关系的方法是存在于 NSOperation 类中的,operation 的依赖关系是它自己管理的,与它被添加到哪个 operation queue 无关
- 特别注意3:千万不要在 operation 之间添加循环依赖,这样会导致这些 operation 都不会被执行。
Demo:
//依赖操作
- (void)operateDependency {
NSMutableArray * array = [NSMutableArray array];
//创建任务
for (int i = 0; i < 10; i++) {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"----第%d个任务%@----", i,[NSThread currentThread]);
}];
op.name = [NSString stringWithFormat:@"op%d",i];
[array addObject:op];
}
//创建队列
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
queue.name = @"queue";
//设置依赖 可以跨队列依赖
for (int i = 0; i < array.count - 1; i++) {
//依次依赖,下面相当于同步执行了
NSBlockOperation * opOne = [array objectAtIndex:i];
NSBlockOperation * opTwo = [array objectAtIndex:i+1];
[opTwo addDependency:opOne];
//修改Operation在队列中的优先级
// if (i == 6) {
// [opOne setQueuePriority:NSOperationQueuePriorityVeryLow];
// [opTwo setQueuePriority:NSOperationQueuePriorityVeryHigh];
// }
//
if (i > 4) {
//删除依赖
// [opTwo removeDependency:opOne];
}
}
//第五个任务完成后就取消对列任务
NSBlockOperation *op = [array objectAtIndex:4];
op.completionBlock = ^{
//取消队列中未执行的所有任务
[queue cancelAllOperations];
};
//添加任务到队列中
[queue addOperations:array waitUntilFinished:NO];
}
六. NSOperation方法介绍
NSOperation方法
BOOL cancelled;//判断是否取消
BOOL executing;//判断任务是否正在执行
BOOL finished;//判断任务是否完成
BOOL concurrent;//判断任务是否并发
NSOPerationQueuePriority queuePriority;//修改operation执行任务线程的优先级
void (^completionBlock)(void)//用来设置完成后需要执行的操作
- (void)cancel;//取消任务
- (void)waitUntilFinish;//阻塞当前线程直到次任务完成
NSOperationQueue方法
NSUInteger operationCount; //获取队列的任务数
- (void)cancelAllOperations;//取消队列中所有的任务
- (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
[queue setSuspended:BOOL];//暂停queue
取消任务
- 当一个 operation 开始执行后,它会一直执行它的任务直到完成或被取消为止。我们可以在任意时间点取消一个 operation ,甚至是在它还未开始执行之前。为了让我们自定义的 operation 能够支持取消事件,我们需要在代码中定期地检查 isCancelled 方法的返回值,一旦检查到这个方法返回 YES ,我们就需要立即停止执行接下来的任务。根据苹果官方的说法,isCancelled 方法本身是足够轻量的,所以就算是频繁地调用它也不会给系统带来太大的负担
- 通常来说,当我们自定义一个 operation 类时,我们需要考虑在以下几个关键点检查 isCancelled 方法的返回值:
- 在真正开始执行任务之前;
- 至少在每次循环中检查一次,而如果一次循环的时间本身就比较长的话,则需要检查得更加频繁;
- 在任何相对来说比较容易中止 operation 的地方。
- 尽管 operation 是支持取消操作的,但却并不是立即取消的,而是在你调用了 operation 的 cancel 方法之后的下一个 isCancelled 的检查点取消的
任务在队列中的优先级
- 对于被添加到 operation queue 中的 operation 来说,决定它们执行顺序的第一要素是它们的 isReady 状态,其次是它们在队列中的优先级。operation 的 isReady 状态取决于它的依赖关系,而在队列中的优先级则是 operation 本身的属性。默认情况下,所有新创建的 operation 的队列优先级都是 normal 的,但是我们可以根据需要通过
setQueuePriority:
方法来提高或降低 operation 的队列优先级。 - 优先级只是大概的判断,跟GCD中的全局队列功能相似,并不能依赖这个做严格的任务顺序
- 队列优先级只应用于相同 operation queue 中的 operation 之间,不同 operation queue 中的 operation 不受此影响
completionBlock
- 一个 operation 可以在它的主任务执行完成时回调一个 completion block 。我们可以用 completion block 来执行一些主任务之外的工作
- 当一个 operation 被取消时,它的 completion block 仍然会执行,所以我们需要在真正执行代码前检查一下 isCancelled 方法的返回值
- 我们也没有办法保证 completion block 被回调时一定是在主线程,理论上它应该是与触发 isFinished 的 KVO 通知所在的线程一致的,所以如果有必要的话我们可以在 completion block 中使用 GCD 来保证从主线程更新 UI
暂停和恢复 Operation Queue
- 暂停执行 operation queue 并不能使正在执行的 operation 暂停执行,而只是简单地暂停调度新的 operation
- 另外,我们并不能单独地暂停执行一个 operation ,除非直接 cancel 掉
七. 并发的NSOperation
在上面创建自定义子类NSOperation任务的时候只是创建了串行的NSOperation子类,只要重写main方法即可。现在我们就来看看如何实现并发的子类NSOperation。
NSOperation有三个状态量isCancelled, isExecuting和isFinished.
实现并发(concurrent)的NSOperation步骤:
1. 重写start()函数
必须的,所有并发执行的 operation 都必须要重写这个方法,替换掉 NSOperation 类中的默认实现。start 方法是一个 operation 的起点,我们可以在这里配置任务执行的线程或者一些其它的执行环境。另外,需要特别注意的是,在我们重写的 start 方法中一定不要调用父类的实现。
2. 重写main函数
可选的,通常这个方法就是专门用来实现与该 operation 相关联的任务的。尽管我们可以直接在 start 方法中执行我们的任务,但是用 main 方法来实现我们的任务可以使设置代码和任务代码得到分离,从而使 operation 的结构更清晰
3. isExecuting 和 isFinished
必须的,并发执行的 operation 需要负责配置它们的执行环境,并且向外界客户报告执行环境的状态。因此,一个并发执行的 operation 必须要维护一些状态信息,用来记录它的任务是否正在执行,是否已经完成执行等。此外,当这两个方法所代表的值发生变化时,我们需要生成相应的 KVO 通知,以便外界能够观察到这些状态的变化。
在并发情况下系统不知道operation什么时候finished, operation里面的task一般来说是异步执行的, 也就是start函数返回了operation不一定就是finish了, 这个你自己来控制, 你什么时候将isFinished置为YES(发送相应的KVO消息), operation就什么时候完成了。
4. 重写isConcurrent函数
必须的,这个方法的返回值用来标识一个 operation 是否是并发的 operation ,我们需要重写这个方法并返回 YES
并发的NSOperationDemo
#import <Foundation/Foundation.h>
typedef void (^CompletionBlock)(NSData* imageData);
@interface ConcurrentOperation : NSOperation
@property (nonatomic, copy) CompletionBlock comBlock;
@end
#import "ConcurrentOperation.h"
@interface ConcurrentOperation()<NSURLConnectionDelegate,NSURLConnectionDataDelegate>
@property (nonatomic, strong) NSURLConnection *connection;
@property (nonatomic, strong) NSMutableData *data;
@property (nonatomic, assign) CFRunLoopRef operationRunLoop;
@end
@implementation ConcurrentOperation
@synthesize executing = _executing;
@synthesize finished = _finished;
- (BOOL)isConcurrent {
return YES;
}
- (void)start {
if (self.isCancelled) {
[self finish];
}
[self willChangeValueForKey:@"isExecuting"];
_executing = YES;
[self didChangeValueForKey:@"isExecuting"];
NSURL *url = [NSURL URLWithString:@"https://ss3.baidu.com/-rVXeDTa2gU2pMbgoY3K/it/u=2573230712,1194340385&fm=202&mola=new&crop=v1"];
NSURLRequest * request = [NSURLRequest requestWithURL:url];
_connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
/*
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(start)
withObject:nil
waitUntilDone:NO];
return;
}
// set up NSURLConnection...
or
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.connection = [NSURLConnection connectionWithRequest:self.request delegate:self];
}];
*/
NSOperationQueue *currentQueue = [NSOperationQueue currentQueue];
BOOL backgroundQueue = (currentQueue != nil && currentQueue != [NSOperationQueue mainQueue]);
NSRunLoop *targetRunloop = (backgroundQueue)?[NSRunLoop currentRunLoop]:[NSRunLoop mainRunLoop];
[self.connection scheduleInRunLoop:targetRunloop forMode:NSRunLoopCommonModes];
[self.connection start];
if (backgroundQueue) {
self.operationRunLoop = CFRunLoopGetCurrent();
CFRunLoopRun();
}
}
- (void)cancel {
if (!_executing) {
return;
}
[super cancel];
[self finish];
}
- (void)finish {
self.connection = nil;
[self willChangeValueForKey:@"isExecuting"];
[self willChangeValueForKey:@"isFinished"];
_executing = NO;
_finished = YES;
[self didChangeValueForKey:@"isExecuting"];
[self didChangeValueForKey:@"isFinished"];
if (self.comBlock) {
self.comBlock(_data);
}
}
#pragma mark - NSURLConnection delegate
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// to do something...
self.data = [NSMutableData data];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// to do something...
NSLog(@"%ld",data.length);
[_data appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if (self.operationRunLoop) {
CFRunLoopStop(self.operationRunLoop);
}
if (self.isCancelled) return;
[self finish];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self finish];
}
@end
并发使用:
//并发的NSOperation子类使用
- (void)concurrentNSOperation {
//子队列运行
ConcurrentOperation * op = [[ConcurrentOperation alloc]init];
op.comBlock = ^(NSData *imageData) {
NSLog(@"before set uiimage iamge:%@",[NSThread currentThread]);
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"after set uiimage iamge:%@",[NSThread currentThread]);
});
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op];
}
并发NSOperation的Demo一些解释:
代码中引入了RunLoop的东西->原因呢:
我们把operation加到了非main queue(或者是在子线程调用的), 那么问题来了, 你会发现NSURLConnection delegate不走了,
主线程会自动创建一个RunLoop来保证程序一直运行. 但子线程默认不创建NSRunLoop, 所以子线程的任务一旦返回, 线程就over了.
上面的并发operation当start函数返回后子线程就退出了, 当NSURLConnection的delegate回调时, 线程已经木有了, 所以你也就收不到回调了. 为了保证子线程持续live(等待connection回调), 你需要在子线程中加入RunLoop, 来保证它不会被kill掉.
Modifying Running Code
线程安全问题
参考文章:https://developer.apple.com/library/archive/technotes/tn2002/tn2059.html#Downloads