您的位置:首页 > 移动开发 > IOS开发

iOS多线程总结

2015-11-15 23:32 405 查看
三种多线程的比较
1> NSThread
优点:相对另外两种,比较轻量级,更加直观控制线程对象
缺点:需要自己管理线程的生命周期和处理线程的同步,线程共享同一应用程序的部分内部空间,他们拥有对数据的访问权限,需要协调多个线程对同一数据的访问,一般的做法是在访问之前加锁,但是会导致性能的开销。
实例方法初始化线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
设置线程的优先级(0.0 - 1.0,1.0最高级)
thread.threadPriority = 1;
开启线程
[thread start];
参数解析:
selector :线程执行的方法,这个selector最多只能接收一个参数
target :selector消息发送的对象
argument : 传给selector的唯一参数,也可以是nil
 
类方法初始化 调用完毕后,会马上创建并开启新线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
 
隐式创建线程的方法
[self performSelectorInBackground:@selector(run) withObject:nil];
获取当前线程
NSThread *current = [NSThreadcurrentThread];
获取主线程
NSThread *main = [NSThreadmainThread];
暂停当前线程 暂停2s
[NSThread sleepForTimeInterval:2];
或者
NSDate *date = [NSDatedateWithTimeInterval:2sinceDate:[NSDate date]];
[NSThread sleepUntilDate:date];
 
线程间的通信
1.在指定线程上执行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
2.在主线程上执行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
3.在当前线程执行操作
[self performSelector:@selector(run) withObject:nil];
 
2> NSOperation
NSOperation实现了封装需要执行的操作和执行操作所需的数据,并且能够以并发或非并发方式执行这个操作。
NSOperation本身就是抽象基类,因此必须使用他的子类,使用NSOperation子类的方式有两种。
--->1 Foudation框架提供了两个具体子类直接提供我们使用:NSInvocationOperation 和 NSBlockOperation
--->2 自定义子类继承NSOperation,实现内部响应的方法
 
执行操作
NSOperation调用start方法即可开始执行操作,默认同步方法执行,isConcurrent返回NO表示操作与调用线程同步执行,返回YES表示与调用线程异步执行。
取消操作
operation开始执行后,默认会一直执行完所有操作,下面的方法可以取消操作
[operation cancel];
监听操作的执行
如果我们想在一个operation执行完后做一些事情,就调用NSOperation的setCompletionBlock方法来设置要执行的操作
operation.completionBlock = ^() {
   NSLog(@"执行的操作");
};
也可以
[operation setCompletionBlock:^(){
   NSLog(@"执行的操作");
}];
二、NSInvocationOperation
1.简介
基于一个对象和selector来创建操作。如果你已经有现有的方法来执行需要的任务,就可以使用这个类
 
2.创建并执行操作
// 这个操作是:调用self的run方法
NSInvocationOperation *operation =[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
// 开始执行任务(同步执行)
[operation start];
 
三、NSBlockOperation
1.简介
能够并发地执行一个或多个block对象,所有相关的block都执行完之后,操作才算完成
 
2.创建并执行操作
NSBlockOperation *operation = [NSBlockOperationblockOperationWithBlock:^(){
   NSLog(@"执行了一个新的操作,线程:%@", [NSThread currentThread]);
}];
// 开始执行任务(这里还是同步执行)
[operation start];
 
3.通过addExecutionBlock方法添加block操作
NSBlockOperation *operation =[NSBlockOperation blockOperationWithBlock:^(){
   NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);
}];
 
[operation addExecutionBlock:^() {
   NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
}];
 
[operation addExecutionBlock:^() {
   NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
}];
 
[operation addExecutionBlock:^() {
   NSLog(@"又执行了1个新的操作,线程:%@", [NSThread currentThread]);
}];
 
// 开始执行任务
[operation start];
   
四、自定义NSOperation
1.简介
如果NSInvocationOperation和NSBlockOperation对象不能满足需求, 你可以直接继承NSOperation, 并添加任何你想要的行为。继承所需的工作量主要取决于你要实现非并发还是并发的NSOperation。定义非并发的NSOperation要简单许多,只需要重载-(void)main这个方法,在这个方法里面执行主任务,并正确地响应取消事件; 对于并发NSOperation, 你必须重写NSOperation的多个基本方法进行实现(这里暂时先介绍非并发的NSOperation)
 
2.非并发的NSOperation
比如叫做DownloadOperation,用来下载图片
1> 继承NSOperation,重写main方法,执行主任务
DownloadOperation.h
#import <Foundation/Foundation.h>
   @protocol DownloadOperationDelegate;
   
   @interface DownloadOperation : NSOperation
// 图片的url路径
@property (nonatomic,copy) NSString *imageUrl;
// 代理
@property (nonatomic,retain)id<DownloadOperationDelegate>delegate;
 
- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate;
@end
   
   // 图片下载的协议
   @protocol DownloadOperationDelegate<NSObject>
- (void)downloadFinishWithImage:(UIImage *)image;
@end
   
   DownloadOperation.m
#import "DownloadOperation.h"
   
   @implementation DownloadOperation
@synthesize delegate = _delegate;
@synthesize imageUrl = _imageUrl;
 
// 初始化
- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate {
   if (self = [super init]) {
        self.imageUrl = url;
        self.delegate = delegate;
   }
   return self;
}
// 释放内存
- (void)dealloc {
   [super dealloc];
   [_delegate release];
   [_imageUrl release];
}
 
// 执行主任务
- (void)main {
   // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
   @autoreleasepool { 
        // .... 
   } 

@end
2> 正确响应取消事件
operation开始执行之后,会一直执行任务直到完成,或者显式地取消操作。取消可能发生在任何时候,甚至在operation执行之前。尽管NSOperation提供了一个方法,让应用取消一个操作,但是识别出取消事件则是我们自己的事情。如果operation直接终止, 可能无法回收所有已分配的内存或资源。因此operation对象需要检测取消事件,并优雅地退出执行
NSOperation对象需要定期地调用isCancelled方法检测操作是否已经被取消,如果返回YES(表示已取消),则立即退出执行。不管是自定义NSOperation子类,还是使用系统提供的两个具体子类,都需要支持取消。isCancelled方法本身非常轻量,可以频繁地调用而不产生大的性能损失
以下地方可能需要调用isCancelled:
* 在执行任何实际的工作之前
* 在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次
* 代码中相对比较容易中止操作的任何地方
3> GCD
简绍:
全称(Grand Center Dispatch),在iOS所有实现的多线程中,GCD应该是最有魅力的,因为GCD本来就是苹果公司为多核的并行运算提出的解决方案,GCD在工作时会自动利用更多的处理核心,以充分利用更强大的机器。他是基于C语言的。如果使用GCD,完全系统管理线程,不需要重现线程代码,只需要关心执行的任务,然后添加到调度队列(dispatch queue),GCD会负责创建线程和调度你的任务,系统直接提供线程管理。
 
调度队列(dispath queue)
1.GCD的一个重要概念是队列,其核心理念是:将长期运行的任务拆分成多个工作单元,将这些单元添加到dispath queue中,系统会为我们管理这些dispath queue,为我们在多个线程上执行工作单元,我们不需要直接启动和管理后台线程。
2.dispath queue按先进先出的顺序,并行或并发地执行任务,
1) serial dis queue一次只能执行一个任务,当前任务完成才能执行下一个任务。
2) concurrent dispath queue则尽可能多地启动任务并发执行
 
创建和管理dispath queue
1.获得全局并发Dispatch Queue (concurrent dispatch queue)
1> 并发dispatch queue可以同时并行地执行多个任务,不过并发queue仍然按先进先出的顺序来启动任务。并发queue会在之前的任务完成之前就出列下一个任务并开始执行。并发queue同时执行的任务数量会根据应用和系统动态变化,各种因素包括:可用核数量、其它进程正在执行的工作数量、其它串行dispatch queue中优先任务的数量等.
2> 系统给每个应用提供三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。你不需要显式地创建这些queue,使用dispatch_get_global_queue函数来获取这三个queue:
// 获取默认优先级的全局并发dispatch queue
dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数目前未使用到,默认0即可
3> 虽然dispatch queue是引用计数的对象,但你不需要retain和release全局并发queue。因为这些queue对应用是全局的,retain和release调用会被忽略。你也不需要存储这三个queue的引用,每次都直接调用dispatch_get_global_queue获得queue就行了。
 
2.创建串行Dispatch Queue (serial dispatch queue)
1> 应用的任务需要按特定顺序执行时,就需要使用串行Dispatch Queue,串行queue每次只能执行一个任务。你可以使用串行queue来替代锁,保护共享资源 或可变的数据结构。和锁不一样的是,串行queue确保任务按可预测的顺序执行。而且只要你异步地提交任务到串行queue,就永远不会产生死锁
2> 你必须显式地创建和管理所有你使用的串行queue,应用可以创建任意数量的串行queue,但不要为了同时执行更多任务而创建更多的串行queue。如果你需要并发地执行大量任务,应该把任务提交到全局并发queue
3> 利用dispatch_queue_create函数创建串行queue,两个参数分别是queue名和一组queue属性
dispatch_queue_t queue;
queue = dispatch_queue_create("hehehehehe",NULL);
 
3.运行时获得公共Queue
GCD提供了函数让应用访问几个公共dispatch queue:
1> 使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。
2> 使用dispatch_get_main_queue函数获得应用主线程关联的串行dispatch queue
3> 使用dispatch_get_global_queue来获得共享的并发queue
 
4.Dispatch Queue的内存管理
1> Dispatch Queue和其它dispatch对象(还有dispatch source)都是引用计数的数据类型。当你创建一个串行dispatch queue时,初始引用计数为1,你可以使用dispatch_retain和dispatch_release函数来增加和减少引用计数。当引用计数到达0 时,系统会异步地销毁这个queue
2> 对dispatch对象(如dispatch queue)retain和release 是很重要的,确保它们被使用时能够保留在内存中。和OC对象一样,通用的规则是如果使用一个传递过来的queue,你应该在使用前retain,使用完之后release
3> 你不需要retain或release全局dispatch queue,包括全局并发dispatch queue和main dispatch queue
4> 即使你实现的是自动垃圾收集的应用,也需要retain和release创建的dispatch queue和其它dispatch对象。GCD 不支持垃圾收集模型来回收内存
 
四、添加任务到queue
要执行一个任务,你需要将它添加到一个适当的dispatch queue,你可以单个或按组来添加,也可以同步或异步地执行一个任务,也。一旦进入到queue,queue会负责尽快地执行你的任务。一般可以用一个block来封装任务内容。
1.添加单个任务到queue
1> 异步添加任务
你可以异步或同步地添加一个任务到Queue,尽可能地使用dispatch_async或dispatch_async_f函数异步地调度任务。因为添加任务到Queue中时,无法确定这些代码什么时候能够执行。因此异步地添加block或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。特别是应用主线程一定要异步地 dispatch 任务,这样才能及时地响应用户事件
2> 同步添加任务
少数时候你可能希望同步地调度任务,以避免竞争条件或其它同步错误。 使用dispatch_sync和dispatch_sync_f函数同步地添加任务到Queue,这两个函数会阻塞当前调用线程,直到相应任务完成执行。注意:绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做。
3> 代码演示
// 调用前,查看下当前线程
NSLog(@"当前调用线程:%@", [NSThread currentThread]);
 
// 创建一个串行queue
dispatch_queue_t queue =dispatch_queue_create("cn.itcast.queue",NULL);
 
dispatch_async(queue, ^{
   NSLog(@"开启了一个异步任务,当前线程:%@", [NSThread currentThread]);
});
 
dispatch_sync(queue, ^{
   NSLog(@"开启了一个同步任务,当前线程:%@", [NSThread currentThread]);
});
// 销毁队列
dispatch_release(queue);
   
2.并发地执行循环迭代
如果你使用循环执行固定次数的迭代, 并发dispatch queue可能会提高性能。
例如下面的for循环:
[java] view plaincopy
int i;
int count = 10;
for (i = 0; i < count; i++) {
   printf("%d  ",i);
}
1> 如果每次迭代执行的任务与其它迭代独立无关,而且循环迭代执行顺序也无关紧要的话,你可以调用dispatch_apply或dispatch_apply_f函数来替换循环。这两个函数为每次循环迭代将指定的block或函数提交到queue。当dispatch到并发 queue时,就有可能同时执行多个循环迭代。用dispatch_apply或dispatch_apply_f时你可以指定串行或并发 queue。并发queue允许同时执行多个循环迭代,而串行queue就没太大必要使用了。
下面代码使用dispatch_apply替换了for循环,你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。第一次迭代这个参数值为0,最后一次值为count -1
   [java] view plaincopy
   // 获得全局并发queue
   dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
   size_t count = 10;
   dispatch_apply(count, queue, ^(size_t i) {
        printf("%zd ", i);
   });
   // 销毁队列
   dispatch_release(queue);
   可以看出,这些迭代是并发执行的
   和普通for循环一样,dispatch_apply和dispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程,主线程中调用这两个函数必须小心,可能会阻止事件处理循环并无法响应用户事件。所以如果循环代码需要一定的时间执行,可以考虑在另一个线程中调用这两个函数。如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。
 
3.在主线程中执行任务
1> GCD提供一个特殊的dispatch queue,可以在应用的主线程中执行任务。只要应用主线程设置了run loop(由CFRunLoopRef类型或NSRunLoop对象管理),就会自动创建这个queue,并且最后会自动销毁。非Cocoa应用如果不显式地设置run loop, 就必须显式地调用dispatch_main函数来显式地激活这个dispatch queue,否则虽然你可以添加任务到queue,但任务永远不会被执行。
2> 调用dispatch_get_main_queue函数获得应用主线程的dispatch queue,添加到这个queue的任务由主线程串行化执行
3> 代码实现,比如异步下载图片后,回到主线程显示图片
[java] view plaincopy
// 异步下载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
   NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"];
   UIImage *image = [UIImage imageWithData:[NSDatadataWithContentsOfURL:url]];
   
   // 回到主线程显示图片
   dispatch_async(dispatch_get_main_queue(), ^{
        self.imageView.image = image;
   });
});
 
4.任务中使用Objective-C对象
GCD支持Cocoa内存管理机制,因此可以在提交到queue的block中自由地使用Objective-C对象。每个dispatch queue维护自己的autorelease pool确保释放autorelease对象,但是queue不保证这些对象实际释放的时间。如果应用消耗大量内存,并且创建大量autorelease对象,你需要创建自己的autorelease pool,用来及时地释放不再使用的对象。
 
五、暂停和继续queue
我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程 编程 技术 iOS