2.GCD(Grand Central Dispatch,苹果为多核的并行运算提出的解决方案)
NSThread
//创建线程类
NSThread *thread=[[NSThread alloc] initWithTarget:self selector:@selector(threadShow) object:nil];
//启动线程类
[thread start];
-(void)threadShow{
UIAlertView *alert=[[UIAlertView alloc] initWithTitle:@"线程执行" message:@"线程启动了" delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
[alert show];
}
NSThread 常见方法
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//获取当前线程信息
+ (NSThread *)currentThread;
//获取主线程信息
+ (NSThread *)mainThread;
//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
GCD(Grand Central Dispatch,苹果为多核的并行运算提出的解决方案)
GCD 是 libdispatch
的市场名称,而 libdispatch 作为 Apple 的一个库,为并发代码在多核硬件(跑 iOS 或 OS X )上执行提供有力支持。它具有以下优点:
- GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。
- GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
- GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。
并发和并行通常被一起提到,所以值得花些时间解释它们之间的区别。虽然你可以编写代码在 GCD 下并发执行,但 GCD 会决定有多少并行的需求。并行要求并发,但并发并不能保证并行。
Serial Queues 串行队列
dispatch_queue_create
串行队列中的任务一次执行一个,每个任务只在前一个任务完成时才开始。而且,你不知道在一个 Block 结束和下一个开始之间的时间长度,唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。
Concurrent Queues 并发队列
dispatch_get_global_queue
在并发队列中的任务能得到的保证是它们会按照被添加的顺序开始执行,但这就是全部的保证了。任务可能以任意顺序完成,你不会知道何时开始运行下一个任务,或者任意时刻有多少 Block 在运行。何时开始一个 Block 完全取决于 GCD 。如果一个 Block 的执行时间与另一个重叠,也是由 GCD 来决定是否将其运行在另一个不同的核心上,如果那个核心可用,否则就用上下文切换的方式来执行不同的 Block 。
Queue Types 队列类型
系统提供给你一个叫做 主队列(main queue)
的特殊队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程,必须总是在主线程访问 UIKit 的类。这个队列就是用于发生消息给 UIView
或发送通知的。
系统同时提供给你好几个并发队列。它们叫做 全局调度队列(Global Dispatch Queues)
。目前的四个全局队列有着不同的优先级:background
、low
、default
以及 high
。要知道,Apple 的 API 也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务。
最后,你也可以创建自己的串行队列或并发队列。这就是说,至少有五个队列任你处置:主队列、四个全局调度队列,再加上任何你自己创建的队列。
在 dispatch_async
上如何以及何时使用不同的队列类型的快速指导:
- 自定义串行队列:当你想串行执行后台任务并追踪它时就是一个好选择。这消除了资源争用,因为你知道一次只有一个任务在执行。注意若你需要来自某个方法的数据,你必须内联另一个 Block 来找回它或考虑使用
dispatch_sync
。 - 主队列(串行):这是在一个并发队列上完成任务后更新 UI 的共同选择。要这样做,你将在一个 Block 内部编写另一个 Block 。以及,如果你在主队列调用
dispatch_async
到主队列,你能确保这个新任务将在当前方法完成后的某个时间执行。 - 并发队列:这是在后台执行非 UI 工作的共同选择。
异步主线程
__weak MyController *weakSelf = self 或者
__weak typeof(self) weakSelf = self;
例子:
typeof(int *) a,b;
等价于:
int *a,*b;
//使用异步在主线程的方式模态加载视图 __weak typeof(self)weakSelf=self; dispatch_async(dispatch_get_main_queue(), ^{ TKOCRImgController *ocrControl=[[TKOCRImgController alloc] initWithParam:reqParam]; if (ocrControl) { ocrControl.delegate=self; [weakSelf presentViewController:ocrControl animated:YEScompletion:nil]; } });
例子:
//将工作从主线程移到全局线程,用priority指定队列的优先级,而flag作为保留字段备用(一般为0) 。因为这是一个 dispatch_async() ,Block 会被异步地提交,意味着调用线程地执行将会继续。 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ UIImage *overlayImage = [self faceOverlayImageFromImage:_image]; dispatch_async(dispatch_get_main_queue(), ^{ //主线程更新UI [self updateUI]; }); });
何时适合使用 dispatch_after
:
dispatch_after
工作起来就像一个延迟版的 dispatch_async
。你依然不能控制实际的执行时间,且一旦 dispatch_after
返回也就不能再取消它。
- 自定义串行队列:在一个自定义串行队列上使用
dispatch_after
要小心。你最好坚持使用主队列。 - 主队列(串行):是使用
dispatch_after
的好选择;Xcode 提供了一个不错的自动完成模版。 - 并发队列:在并发队列上使用
dispatch_after
也要小心;你会这样做就比较罕见。还是在主队列做这些操作吧。
//异步2秒之后回到主线程执行 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2. * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self myFunction]; });
//例
//声明了一个变量指定要延迟的时长。 double delayInSeconds = 1.0; //然后等待 delayInSeconds 给定的时长 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); //再异步地添加一个 Block 到主线程。 dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ if (flag) { //到导航控制器上添加提示 [self.navigationItem setPrompt:@"Add photos with faces to Googlyify them!"]; } else { [self.navigationItem setPrompt:nil]; } });
dispatch_sync()
:
同步地提交工作并在返回前等待它完成。使用 dispatch_sync
跟踪你的调度障碍工作,或者当你需要等待操作完成后才能使用 Block 处理过的数据。
例如:异步子线程某段代码需要主线程操作又需要保证执行顺序就用同步主线程;后续代码还是走原来的线程
__block UIInterfaceOrientation orientation; dispatch_sync(dispatch_get_main_queue(), ^{ orientation= [UIApplication sharedApplication].statusBarOrientation; });
Dispatch Groups(调度组):
Dispatch Group 会在整个组的任务都完成时通知你。这些任务可以是同步的,也可以是异步的,即便在不同的队列也行。而且在整个组的任务都完成时,Dispatch Group 可以用同步的或者异步的方式通知你。因为要监控的任务在不同队列,那就用一个 dispatch_group_t 的实例来记下这些不同的任务。
当组中所有的事件都完成时,GCD 的 API 提供了两种通知方式。
第一种是 dispatch_group_wait ,它会阻塞当前线程,直到组里面所有的任务都完成或者等到某个超时发生。
第二种是dispatch_group_notify 以异步的方式工作。
//下面是dispatch_group_notify方式通知 //创建一个新的 Dispatch Group,它的作用就像一个用于未完成任务的计数器。 dispatch_group_t group = dispatch_group_create(); //你必须保证 dispatch_group_enter 和 dispatch_group_leave 成对出现,否则你可能会遇到诡异的崩溃问题。 //dispatch_group_enter 手动通知 Dispatch Group 任务已经开始。 dispatch_group_enter(group); // 进行操作,也可以使用Block,在回调里leave //doSomething //dispatch_group_leave手动通知Dispatch Group它的工作已经完成 dispatch_group_leave(group); dispatch_group_enter(group); // 进行操作,也可以使用Block,在回调里leave //doSomething dispatch_group_leave(group); dispatch_group_enter(group); // 进行操作,也可以使用Block,在回调里leave //doSomething dispatch_group_leave(group); //dispatch_group_notify 以异步的方式工作。当 Dispatch Group 中没有任何任务时,它就会执行其代码。(不像dispatch_group_wait是同步的要用 dispatch_async 将整个方法放入后台队列以避免阻塞主线程)。 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ //主队列里发出通知,或者更新UI });
dispatch_apply
:
表现得就像一个 for
循环,但它能并发地执行不同的迭代。这个函数是同步的,所以和普通的 for
循环一样,它只会在所有工作都完成后才会返回。
//创建一个新的 Dispatch Group,它的作用就像一个用于未完成任务的计数器。 dispatch_group_t group = dispatch_group_create(); //第一个参数指明了迭代的次数,用第二个参数指定了任务运行的队列,而第三个参数是一个 Block。 dispatch_apply(3, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) { //你必须保证 dispatch_group_enter 和 dispatch_group_leave 成对出现,否则你可能会遇到诡异的崩溃问题。 //dispatch_group_enter 手动通知 Dispatch Group 任务已经开始。 dispatch_group_enter(group); // 进行操作,也可以使用Block,在回调里leave //doSomething //dispatch_group_leave手动通知Dispatch Group它的工作已经完成 dispatch_group_leave(group); }); //dispatch_group_notify 以异步的方式工作。当 Dispatch Group 中没有任何任务时,它就会执行其代码。(不像dispatch_group_wait是同步的要用 dispatch_async 将整个方法放入后台队列以避免阻塞主线程)。 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ //主队列里发出通知,或者更新UI });
dispatch_barrier_async
dispatch_barrier_async起到了承上启下的作用。它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。正如barrier的含义一样,它起到一个栅栏或者分水岭的作用。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ }); dispatch_barrier_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ });
信号量:
信号量是一种老式的线程概念,控制多个消费者对有限数量资源的访问。举例来说,如果你创建了一个有着两个资源的信号量,那同时最多只能有两个线程可以访问临界区。其他想使用资源的线程必须在一个FIFO队列里等待。
// 创建一个信号量。参数指定信号量的起始值。这个数字是你可以访问的信号量,不需要有人先去增加它的数量。(注意到增加信号量也被叫做发射信号量)。这里初始化为0,也就是说,有人想使用信号量必然会被阻塞,直到有人增加信号量。 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); // 进行操作,也可以使用Block,在回调里leave //doSomething // 告诉信号量你不再需要资源了。这就会增加信号量的计数并告知其他想使用此资源的线程。 dispatch_semaphore_signal(semaphore); // 这会在超时之前等待信号量。这个调用阻塞了当前线程直到信号量被发射。这个函数的一个非零返回值表示到达超时了。 dispatch_time_t timeoutTime = dispatch_time(DISPATCH_TIME_NOW, kDefaultTimeoutLengthInNanoSeconds); if (dispatch_semaphore_wait(semaphore, timeoutTime)) { XCTFail(@"%@ timed out", URLString); }
Dispatch Source:
GCD 的一个特别有趣的特性是 Dispatch Source,它基本上就是一个低级函数的 grab-bag ,能帮助你去响应或监测 Unix 信号、文件描述符、Mach 端口、VFS 节点,以及其它晦涩的东西。
//下面是创建一个源的函数原型: dispatch_source_t dispatch_source_create( dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue);] //dispatch_source_type_t 。这是最重要的参数,因为它决定了 handle 和 mask 参数将会是什么。 //下面将监控 DISPATCH_SOURCE_TYPE_SIGNAL // 最好是在 DEBUG 模式下编译这些代码,因为这会给“有关方面(Interested Parties)”很多关于你应用的洞察。 #if DEBUG // 创建了一个 dispatch_queue_t 实例变量而不是在参数上直接使用函数。当代码变长,分拆有助于可读性。 dispatch_queue_t queue = dispatch_get_main_queue(); // 需要 source 在方法范围之外也可被访问,所以你使用了一个 static 变量。 static dispatch_source_t source = nil; // 使用 weakSelf 以确保不会出现保留环(Retain Cycle) __typeof(self) __weak weakSelf = self; // 使用 dispatch_once 确保只会执行一次 Dispatch Source 的设置。 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 初始化 source 变量。你指明了你对信号监控感兴趣并提供了 SIGSTOP 信号作为第二个参数。进一步,你使用主队列处理接收到的事件——很快你就好发现为何要这样做。 source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGSTOP, 0, queue); // 如果提供的参数不合格,那么 Dispatch Source 对象不会被创建。也就是说,在你开始在其上工作之前,你需要确保已有了一个有效的 Dispatch Source 。 if (source) { // 当收到你所监控的信号时,dispatch_source_set_event_handler 就会执行。之后你可以在其 Block 里设置合适的逻辑处理器(Logic Handler)。 dispatch_source_set_event_handler(source, ^{ // 一个基本的 NSLog 语句,它将对象打印到控制台。 NSLog(@"Hi, I am: %@", weakSelf); }); //默认的,所有源都初始为暂停状态。如果你要开始监控事件,你必须告诉源对象恢复活跃状态。 dispatch_resume(source); } }); #endif
简介:和GCD一样,NSOperation也是苹果提供给我们的一套多线程解决方案。实际上它也是基于GCD开发的,但是比GCD拥有更强的可控性和代码可读性。
NSOperation是一个抽象基类,基本没有什么实际使用价值。我们使用最多的是系统封装好的NSInvocationOperation和NSBlockOperation。
不过NSOperation一些通用的方法你要知道
NSOperation * operation = [[NSOperation alloc]init]; //开始执行 [operation start]; //取消执行 [operation cancel]; //执行结束后调用的Block [operation setCompletionBlock:^{ NSLog(@"执行结束"); }];
NSBlockOperation
NSBlockOperation也是NSOperation的子类,支持并发的实行一个或多个block;NSBlockOperation确实实现了多线程,它并非是将所有的block都放到放到了子线程中,它会优先将block放到主线程中执行,若主线程已有待执行的代码,就开辟新的线程,线程上线依据运行环境而有不同
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1在第%@个线程", [NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@"2在第%@个线程",[NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@"3在第%@个线程",[NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@"4在第%@个线程",[NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@"5在第%@个线程",[NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@"6在第%@个线程",[NSThread currentThread]); }]; [blockOperation start];
NSInvocationOperation
NSInvocationOperation的使用方式和给Button添加事件比较相似,需要一个对象和一个Selector
//创建 NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSOperation) object:nil]; //执行 [invo start];
- (void)testNSOperation { NSLog(@"我在第%@个线程",[NSThread currentThread]); }
NSOPerationQueue
使用NSOperationQueue来执行任务与之前的区别在于,首先创建一个非主队列。然后用addOperation方法替换之前的start方法。刚刚已经说过,NSOperationQueue会为每一个NSOperation创建线程并调用它们的start方法。
//方式1
NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSOperation) object:nil]; NSOperationQueue * queue = [[NSOperationQueue alloc]init]; [queue addOperation:invo];
- (void)testNSOperation { NSLog(@"我在第%@个线程",[NSThread currentThread]); }
//方式2
//自建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"task0---%@", [NSThread currentThread]); }]; [op addExecutionBlock:^{ NSLog(@"task1---%@", [NSThread currentThread]); }]; [op addExecutionBlock:^{ NSLog(@"task2---%@", [NSThread currentThread]); }]; [op addExecutionBlock:^{ NSLog(@"task3---%@", [NSThread currentThread]); }]; [queue addOperation:op]; NSLog(@"操作结束");
NSOperationQueue的添加依赖的功能
使用依赖关系有三点需要注意
1.不要建立循环依赖,会造成死锁,原因同循环引用
2.使用依赖建议只使用NSInvocationOperation,NSInvocationOperation和NSBlockOperation混用会导致依赖关系无法正常实现。
3.依赖关系不光在同队列中生效,不同队列的NSOperation对象之前设置的依赖关系一样会生效
添加依赖的代码最好放到添加队列之前,NSOperationQueue会在『合适』的时间自动去执行任务,因此你无法确定它到底何时执行,有可能前一秒添加的任务,在你这一秒准备添加依赖的时候就已经执行完了,就会出现依赖无效的假象。
//举个例子,假如A依赖于B,那么在B执行结束之前,A将永远不会执行
NSInvocationOperation * op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation1) object:nil]; NSInvocationOperation * op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation2) object:nil]; NSOperationQueue * queue = [[NSOperationQueue alloc]init]; [op2 addDependency:op1]; [queue addOperation:op1]; [queue addOperation:op2];
- (void)testNSInvocationOperation1 { NSLog(@"我是op1 我在第%@个线程",[NSThread currentThread]); } - (void)testNSInvocationOperation2 { NSLog(@"我是op2 我在第%@个线程",[NSThread currentThread]); }