iOS多线程总结

iOS多线程总结

一、iOS多线程对比

1.NSThread
每一个NSThread对象对应一个线程,真正最原始的线程。
1)优势:NSThread 轻量级最低,相对简单。
2)缺点:手动管理全部的线程活动,如生命周期、线程同步、睡眠等。编程

2.NSOperation
自带线程管理的抽象类。
1)优势:自带线程周期管理,操做上可更注重本身逻辑。
2)缺点:面向对象的抽象类,只能实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。后端

3.GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。
1)优势:最高效,避开并发陷阱。
2)缺点:基于C实现。api

4.选择小结
1)简单而安全的选择NSOperation实现多线程便可。
2)处理大量并发数据,又追求性能效率的选择GCD。
3) 在频繁使用多线程的程序中通常不建议使用NSThread安全

二、NSThread

NSThread是Objective-C 中提供的对POSIX 线程 API的封装。服务器

直接使用线程可能会引起的一个问题是,若是你的代码和所基于的框架代码都建立本身的线程时,那么活动的线程数量有可能以指数级增加。这在大型工程中是一个常见问题。例如,在 8 核 CPU 中,你建立了 8 个线程来彻底发挥 CPU 性能。然而在这些线程中你的代码所调用的框架代码也作了一样事情(由于它并不知道你已经建立的这些线程),这样会很快产生成成百上千的线程。代码的每一个部分自身都没有问题,然而最后却仍是致使了问题。使用线程并非没有代价的,每一个线程都会消耗一些内存和内核资源。网络

2.一、三种实现开启线程方式

动态实例化

NSThread *thread = [[NSThread alloc] initWithTarget:self            selector:@selector(loadImageSource:) object:imgUrl];

thread.threadPriority = 1;// 设置线程的优先级(0.0 - 1.0,1.0最高级)
[thread start];

静态实例化

[NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:imgUrl];

隐式实例化

[self performSelectorInBackground:@selector(loadImageSource:) withObject:imgUrl];

//在指定线程上操做
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];

自定义

须要继承NSThread,而且重写main方法,而后经过start方法启动。多线程

2.二、常见操做方式

//取消线程
- (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 Centra Dispatch)

经过 GCD,开发者不用再直接跟线程打交道了,只须要向队列中添加代码块便可,GCD 在后端管理着一个线程池。GCD 不只决定着你的代码块将在哪一个线程被执行,它还根据可用的系统资源对这些线程进行管理。这样能够将开发者从线程管理的工做中解放出来,经过集中的管理线程,来缓解大量线程被建立的问题。并发

GCD 带来的另外一个重要改变是,做为开发者能够将工做考虑为一个队列,而不是一堆线程,这种并行的抽象模型更容易掌握和使用。app

GCD 公开有 5 个不一样的队列:运行在主线程中的 main queue,3 个不一样优先级的后台队列,以及一个优先级更低的后台队列(用于 I/O)。 另外,开发者能够建立自定义队列:串行或者并行队列。自定义队列很是强大,在自定义队列中被调度的全部 block 最终都将被放入到系统的全局队列中和线程池中。框架

<center>image_1ap71r5b01bfa96hrgk5971noa9.png-39kB</center>

在绝大多数状况下使用默认的优先级队列就能够了。若是执行的任务须要访问一些共享的资源,那么在不一样优先级的队列中调度这些任务很快就会形成不可预期的行为。这样可能会引发程序的彻底挂起,由于低优先级的任务阻塞了高优先级任务,使它不能被执行。

3.一、三种线程队列类型

主线程队列 main queue

它是全局可用的serial queue,它是在应用程序主线程上执行任务的。

dispatch_get_main_queue()

并行队列global dispatch queue

又称为global dispatch queue,能够并发地执行多个任务,可是执行完成的顺序是随机的。

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

这里的两个参数得说明一下:第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数目前未使用到,默认0便可。

串行队列serial queues

又称为private dispatch queues,同时只执行一个任务。Serial queue一般用于同步访问特定的资源或数据。当你建立多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。

dispatch_queue_create("minggo.app.com", NULL);

凡是本身建立的队列默认为串行队列。可在建立参数上添加DISPATCH_QUEUE_CONCURRENT,构建并行队列。

//串行队列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
  //并行队列
  dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);

3.二、两种线程类型

同步线程

dispatch_sync(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

异步线程

dispatch_async(<#queue#>, ^{
      //code here
      NSLog(@"%@", [NSThread currentThread]);
  });

3.三、6种多线程实现

后台执行线程建立

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [self loadImageSource:imgUrl1];
});

UI线程执行(只是为了测试,长时间加载内容不放在主线程)

dispatch_async(dispatch_get_main_queue(), ^{
    [self loadImageSource:imgUrl1];
});

一次性执行(经常使用来写单例)

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    [self loadImageSource:imgUrl1];
});

并发地执行循环迭代

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10;
dispatch_apply(count, queue, ^(size_t i) {
    NSLog(@"循环执行第%li次",i);
    [self loadImageSource:imgUrl1];
});

延迟执行

double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self loadImageSource:imgUrl1];
});

自定义dispatch_queue_t

dispatch_queue_t urls_queue = dispatch_queue_create("minggo.app.com", NULL);
dispatch_async(urls_queue, ^{
    [self loadImageSource:imgUrl1];
});

3.四、队列组(dispatch_group_t)

队列组能够将不少队列添加到一个组里,这样作的好处是,当这个组里全部的任务都执行完了,队列组会经过一个方法通知咱们。

dispatch_group_async能够实现监听一组任务是否完成,完成后获得通知执行其余的操做。这个方法颇有用,好比你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码

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, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group1");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group2");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"updateUi");
    });
    dispatch_release(group);

3.五、其余用法

dispatch_barrier_async的使用

dispatch_barrier_async是在前面的任务执行结束后它才执行,并且它后面的任务等它执行完成以后才会执行。

dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);  
dispatch_async(queue, ^{  
    [NSThread sleepForTimeInterval:2];  
    NSLog(@"dispatch_async1");  
});  
dispatch_async(queue, ^{  
    [NSThread sleepForTimeInterval:4];  
    NSLog(@"dispatch_async2");  
});  
dispatch_barrier_async(queue, ^{  
    NSLog(@"dispatch_barrier_async");  
    [NSThread sleepForTimeInterval:4];  
  
});  
dispatch_async(queue, ^{  
    [NSThread sleepForTimeInterval:1];  
    NSLog(@"dispatch_async3");  
});

打印结果:
2012-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2
2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async
2012-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3

dispatch_apply的使用

执行某个代码片断N次。

dispatch_apply(5, globalQ, ^(size_t index) {
    // 执行5次
});

四、NSOperation

NSOperation 是苹果公司对 GCD 的封装,彻底面向对象,NSOperationNSOperationQueue 分别对应 GCD 的任务和队列 。操做步骤也很好理解:

  1. 将要执行的任务封装到一个 NSOperation 对象中。

  2. 将此任务添加到一个 NSOperationQueue 对象中。

4.一、NSOperation类型

NSOperation 只是一个抽象类,因此不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperationNSBlockOperation 。建立一个 Operation 后,须要调用 start 方法来启动任务,它会 默认在当前队列同步执行。固然你也能够在中途取消一个任务,只须要调用其 cancel方法便可。

//1.建立NSInvocationOperation对象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

 //2.开始执行
 [operation start];
  
  //1.建立NSBlockOperation对象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

  //2.开始任务
  [operation start];

自定义Operation

除了上面的两种 Operation 之外,咱们还能够自定义 Operation。自定义 Operation 须要继承 NSOperation 类,并实现其 main() 方法,由于在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。因此若是以上的两个类没法知足你的欲望的时候,你就须要自定义了。你想要实现什么功能均可以写在里面。除此以外,你还须要实现 cancel() 在内的各类方法。

若是是须要并发执行的话,还必需要重写
start

asynchronous

executing

finished
方法。

4.二、两种队列(NSOperation)

NSOperationQueue 有两种不一样类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。在两种类型中,这些队列所处理的任务都使用 NSOperation 的子类来表述。

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];  //主队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //自定义队列

//添加一个NSOperation
[queue addOperation:operation]

//添加一组NSOperation
[queue addOperations:operations waitUntilFinished:NO

//添加一个block形式的Operation
[queue addOperationWithBlock:^{
    //执行一个Block的操做        
}];

[queue setMaxConcurrentOperationCount:1];

//单个NSOperation取消
[operation cancel]

//取消NSOperationQueue中的全部操做
[queue cancelAllOperations]

// 暂停queue  
[queue setSuspended:YES];  
  
// 继续queue  
[queue setSuspended:NO];

咱们能够经过设置maxConcurrentOperationCount 属性来控制并发任务的数量,当设置为 1 时, 那么它就是一个串行队列。主对列默认是串行队列,这一点和 dispatch_queue_t是类似的。

NSOperation 有一个很是实用的功能,那就是添加依赖。好比有 3 个任务:A: 从服务器上下载一张图片,B:给这张图片加个水印,C:把图片返回给服务器。这时就能够用到依赖了:

//1.任务一:下载图片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任务二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任务三:上传图片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上传图片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.设置依赖
[operation2 addDependency:operation1];      //任务二依赖任务一
[operation3 addDependency:operation2];      //任务三依赖任务二

//5.建立队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

五、使用选择

目前在 iOS 和 OS X 中有两套先进的同步 API 可供咱们使用:NSOperation 和 GCD 。其中 GCD 是基于 C 的底层的 API ,而 NSOperation 则是 GCD 实现的 Objective-C API。 虽然 NSOperation 是基于 GCD 实现的, 可是并不意味着它是一个 GCD 的 “dumbed-down” 版本, 相反,咱们能够用NSOperation 轻易的实现一些 GCD 要写大量代码的事情。 所以, NSOperationQueue 是被推荐使用的, 除非你遇到了 NSOperationQueue 不能实现的问题。

为何优先使用NSOperationQueue而不是GCD

曾经我有一段时间我很是喜欢使用GCD来进行并发编程,由于虽然它是C的api,可是使用起来却很是简单和方便, 不过这样也就容易使开发者忘记并发编程中的许多注意事项和陷阱。
好比你可能写过相似这样的代码(这样来请求网络数据):

dispatch_async(_Queue, ^{
  
//请求数据
NSData *data = [NSData dataWithContentURL:[NSURL URLWithString:@"http://domain.com/a.png"]];

    dispatch_async(dispatch_get_main_queue(), ^{

         [self refreshViews:data];
    });
});

没错,它是能够正常的工做,可是有个致命的问题:这个任务是没法取消的 dataWithContentURL:是同步的拉取数据,它会一直阻塞线程直到完成请求,若是是遇到了超时的状况,它在这个时间内会一直占有这个线程;在这个期间并发队列就须要为其余任务新建线程,这样可能致使性能降低等问题。
所以咱们不推荐这种写法来从网络拉取数据。

操做队列(operation queue)是由 GCD 提供的一个队列模型的 Cocoa 抽象。GCD 提供了更加底层的控制,而操做队列则在 GCD 之上实现了一些方便的功能,这些功能对于 app 的开发者来讲一般是最好最安全的选择。NSOperationQueue相对于GCD来讲有如下优势:

  • 提供了在 GCD 中不那么容易复制的有用特性。

  • 能够很方便的取消一个NSOperation的执行

  • 能够更容易的添加任务的依赖关系

  • 提供了任务的状态:isExecuteing, isFinished.


参考文章
一、谈iOS多线程(NSThread、NSOperation、GCD)编程
二、并发编程:API 及挑战
三、iOS多线程编程之Grand Central Dispatch(GCD)介绍和使用
四、Cocoa深刻学习:NSOperationQueue、NSRunLoop和线程安全

相关文章
相关标签/搜索