线程池NSoperationQueue和GCD比较

本文深入探讨了iOS开发中的多线程技术,包括NSOperation和GCD的使用方法及注意事项,介绍了这两种技术的特点和适用场景,并通过实例演示了如何在iOS应用中实现高效的多线程管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

直接alloc创建的线程都是需要手动启动并且都是同步的、不用

分类: Object-C

2013-10-12 17:19 2296人阅读 评论(0) 收藏 举报

GCD是基于c的底层apiNSOperation属于object-c类。ios 首先引入的是NSOperationIOS4之后引入了GCDNSOperationQueue并且其内部是用gcd实现的。

相对于GCD

1NSOperation拥有更多的函数可用,具体查看api

2,在NSOperationQueue中,可以建立各个NSOperation之间的依赖关系。

3,有kvo,可以监测operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld)。

4NSOperationQueue可以方便的管理并发、NSOperation之间的优先级。

GCD主要与block结合使用。代码简洁高效。

  GCD也可以实现复杂的多线程应用,主要是建立个个线程时间的依赖关系这类的情况,但是需要自己实现相比NSOperation要复杂。

具体使用哪个,依需求而定。 从个人使用的感觉来看,比较合适的用法是:除了依赖关系尽量使用GCD,因为苹果专门为GCD做了性能上面的优化。


24.NSoperationQueue常见的创建异步线程的方式有两种:

1.NSInvocationOperation,

 NSInvocationOperation*operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(dealTask) object:nil];

2.NSBlockOperation

NSInvocationOperation一样同属NSOperation的具体子类,但不同的是以block的形式对程序进行封装,block本身没有提供显示的传入参数和返回结果。

NSBlockOperation* blockOperation = [NSBlockOperation blockOperationWithBlock: ^{

    // to do the task.

}];

//主线程回调

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

            NSLog(@"下载完成");

            UIAlertView *alertView = [[UIAlertView alloc] init];

            alertView.title = @"下载完成";

            [alertView addButtonWithTitle:@"取消"];

            [alertView show];

        }];


25.NSoperationQueue实现线程优先级和数目的管理

[queue setMaxConcurrentOperationCount:5];
线程池中的线程数,也就是并发操作数。

默认情况下是-1-1表示没有限制,这样会同时运行队列中的全部的操作。

要是想通过线程池使线程依次执行,不并发的话,要先设置并发操作数为1,只允许一个线程执行

[queue setMaxConcurrentOperationCount:1];

NSoperationQueue优先级的管理


一般来说,线程池中线程的执行顺序是依次执行的,优先级是默认的,不是特别理解下面是网友分享的:


一个NSOperation对象可以通过调用start方法来执行任务,默认是同步执行的。也可以将NSOperation添加到一个NSOperationQueue(操作队列)中去执行,而且是异步执行的

创建一个操作队列:


[java] view plaincopy


  1. NSOperationQueue *queue = [[NSOperationQueue alloc] init];  


二、添加NSOperationNSOperationQueue


1.添加一个operation


[java] view plaincopy


  1. [queue addOperation:operation];  


2.添加一组operation



[java] view plaincopy


  1. [queue addOperations:operations waitUntilFinished:NO];  


3.添加一个block形式的operation



[java] view plaincopy


  1. [queue addOperationWithBlock:^() {  
  2.     NSLog(@"执行一个新的操作,线程:%@", [NSThread currentThread]);  
  3. }];  


NSOperation添加到queue之后,通常短时间内就会得到运行。但是如果存在依赖,或者整个queue被暂停等原因,也可能需要等待。


注意NSOperation添加到queue之后,绝对不要再修改NSOperation对象的状态。因为NSOperation对象可能会在任何时候运行,因此改变NSOperation对象的依赖或数据会产生不利的影响。你只能查看NSOperation对象的状态, 比如是否正在运行、等待运行、已经完成等


三、添加NSOperation的依赖对象
1.
当某个NSOperation对象依赖于其它NSOperation对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。

[java] view plaincopy


  1. [operation2 addDependency:operation1];  

依赖关系不局限于相同queue中的NSOperation对象,NSOperation对象会管理自己的依赖, 因此完全可以在不同的queue之间的NSOperation对象创建依赖关系


1359821609_3145.png

唯一的限制是不能创建环形依赖,比如A依赖BB依赖A,这是错误的


2.依赖关系会影响到NSOperation对象在queue中的执行顺序,看下面的例子:

1> 没有设置依赖关系


[java] view plaincopy


  1. NSOperationQueue *queue = [[NSOperationQueue alloc] init];  
  2.   
  3. NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){  
  4.     NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);  
  5. }];  
  6.   
  7. NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){  
  8.     NSLog(@"执行第2次操作,线程:%@", [NSThread currentThread]);  
  9. }];  
  10.   
  11. [queue addOperation:operation1];  
  12. [queue addOperation:operation2];  

打印信息:

[java] view plaincopy


  1. 2013-02-03 00:21:35.024 thread[5616:3d13] 执行第1次操作,线程:<NSThread: 0x7658570>{name = (null), num = 3}  
  2. 2013-02-03 00:21:35.063 thread[5616:1303执行第2次操作,线程:<NSThread: 0x765a2e0>{name = (null), num = 4}  

可以看出,默认是按照添加顺序执行的,先执行operation1,再执行operation2



2> 设置了依赖关系


[java] view plaincopy


  1. NSOperationQueue *queue = [[NSOperationQueue alloc] init];  
  2.   
  3. NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){  
  4.     NSLog(@"执行第1次操作,线程:%@", [NSThread currentThread]);  
  5. }];  
  6.   
  7. NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){  
  8.     NSLog(@"执行第2次操作,线程:%@", [NSThread currentThread]);  
  9. }];  
  10. // operation1依赖于operation2  
  11. [operation1 addDependency:operation2];  
  12.   
  13. [queue addOperation:operation1];  
  14. [queue addOperation:operation2];  

打印信息:



[java] view plaincopy


  1. 2013-02-03 00:24:16.260 thread[5656:1b03] 执行第2次操作,线程:<NSThread: 0x7634490>{name = (null), num = 3}  
  2. 2013-02-03 00:24:16.285 thread[5656:1303执行第1次操作,线程:<NSThread: 0x9138b50>{name = (null), num = 4}  

可以看出,先执行operation2,再执行operation1



四、修改Operations的执行顺序

对于添加到queue中的operations,它们的执行顺序取决于2点:

1.首先看看NSOperation是否已经准备好:是否准备好由对象的依赖关系确定

2.然后再根据所有NSOperation的相对优先级来确定。优先级等级则是operation对象本身的一个属性。默认所有operation都拥有普通优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operation queue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。

注意:优先级不能替代依赖关系,优先级只是对已经准备好的 operations确定执行顺序。先满足依赖关系,然后再根据优先级从所有准备好的操作中选择优先级最高的那个执行。


五、设置队列的最大并发操作数量

队列的最大并发操作数量,意思是队列中最多同时运行几条线程

虽然NSOperationQueue类设计用于并发执行Operations,你也可以强制单个queue一次只能执行一个OperationsetMaxConcurrentOperationCount:方法可以配置queue的最大并发操作数量。设为1就表示queue每次只能执行一个操作。不过operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等。因此串行化的operation queue并不等同于GCD中的串行dispatch queue


[java] view plaincopy


  1. // 每次只能执行一个操作  
  2. queue.maxConcurrentOperationCount = 1;  
  3. // 或者这样写  
  4. [queue setMaxConcurrentOperationCount:1];  


六、取消Operations


一旦添加到operation queue,queue就拥有了这个Operation对象并且不能被删除,唯一能做的事情是取消。你可以调用Operation对象的cancel方法取消单个操作,也可以调用operation queuecancelAllOperations方法取消当前queue中的所有操作。

[java] view plaincopy


  1. // 取消单个操作  
  2. [operation cancel];  
  3.   
  4. // 取消queue中所有的操作  
  5. [queue cancelAllOperations];  


七、等待Options完成

为了最佳的性能,你应该设计你的应用尽可能地异步操作,让应用在Operation正在执行时可以去处理其它事情。如果需要在当前线程中处理operation完成后的结果,可以使用NSOperationwaitUntilFinished方法阻塞当前线程,等待operation完成。通常我们应该避免编写这样的代码,阻塞当前线程可能是一种简便的解决方案,但是它引入了更多的串行代码,限制了整个应用的并发性,同时也降低了用户体验。绝对不要在应用主线程中等待一个Operation,只能在第二或次要线程中等待。阻塞主线程将导致应用无法响应用户事件,应用也将表现为无响应。


[java] view plaincopy


  1. // 会阻塞当前线程,等到某个operation执行完毕  
  2. [operation waitUntilFinished];  



除了等待单个Operation完成,你也可以同时等待一个queue中的所有操作,使用NSOperationQueuewaitUntilAllOperationsAreFinished方法。注意:在等待一个 queue,应用的其它线程仍然可以往queue中添加Operation,因此可能会加长线程的等待时间。


[java] view plaincopy


  1. // 阻塞当前线程,等待queue的所有操作执行完毕  
  2. [queue waitUntilAllOperationsAreFinished];  


八、暂停和继续queue


如果你想临时暂停Operations的执行,可以使用queuesetSuspended:方法暂停queue。不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。你可以在响应用户请求时,暂停一个queue来暂停等待中的任务。稍后根据用户的请求,可以再次调用setSuspended:方法继续queueoperation的执行


[java] view plaincopy


  1. // 暂停queue  
  2. [queue setSuspended:YES];  
  3.   
  4. // 继续queue  
  5. [queue setSuspended:NO];  



26.NSThread的相关知识,建立和回调主线程

[NSThread sleepForTimeInterval:10];

 //设置暂停时间,每隔10秒执行一次

这个语句适用于三种线程控制方式

//细节: alloc+init创建的线程不会自动执行,需要start

 11.   NSThread *t1 = [[NSThread alloc] initWithTarget:self selector:@selector(task1) object:nil];

    [t1 start];

//这种方式创建的线程是同步的,一般不采取这种方式。貌似其他两种方式中用手动启动的线程都是同步的,尽量不要使用,用户交互性特别差。

    

    //类方法创建的线程自动执行—>新建线程,——》这种方式是异步的

  22. [NSThread detachNewThreadSelector:@selector(task2:) toTarget:self withObject:t1];

//在子线程中调用主线程的方法.工作线程中不能更改UI控件需要回到主线程,这时候需要回调到主线程

   33.  [self performSelectorOnMainThread:@selector(updateUI) withObject:self waitUntilDone:YES];

27.GCD

介绍:

Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 iOS 4及以上也可用。

设计:

GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。

一个任务可以是一个函数(function)或者是一个block GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。

GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行
dispatch queue分为下面三种:

Serial      

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

Concurrent

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

Main dispatch queue

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

我们看看dispatch queue如何使用

1、常用的方法dispatch_async

为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。

GCD实现这个流程的操作比前面介绍的NSThread  NSOperation的方法都要简单。代码框架结构如下:


[cpp] view plaincopy


  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  2.     // 耗时的操作  
  3.     dispatch_async(dispatch_get_main_queue(), ^{  
  4.         // 更新界面  
  5.     });  
  6. });  

如果这样还不清晰的话,那我们还是用上两篇博客中的下载图片为例子,代码如下:



[cpp] view plaincopy


  1. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  2.     NSURL * url = [NSURL URLWithString:@"http://avatar.youkuaiyun.com/2/C/D/1_totogo2010.jpg"];  
  3.     NSData * data = [[NSData alloc]initWithContentsOfURL:url];  
  4.     UIImage *image = [[UIImage alloc]initWithData:data];  
  5.     if (data != nil) {  
  6.         dispatch_async(dispatch_get_main_queue(), ^{  
  7.             self.imageView.image = image;  
  8.          });  
  9.     }  
  10. });  


而且GCD会自动根据任务在多核处理器上分配资源,优化程序。

系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列,如下:


[cpp] view plaincopy


  1. dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);    


这里也用到了系统默认就有一个串行队列main_queue


[cpp] view plaincopy


  1. dispatch_queue_t mainQ = dispatch_get_main_queue();    


虽然dispatch queue是引用计数的对象,但是以上两个都是全局的队列,不用retainrelease

2dispatch_group_async的使用

dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码:


[cpp] view plaincopy


  1. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
  2. dispatch_group_t group = dispatch_group_create();  
  3. dispatch_group_async(group, queue, ^{  
  4.     [NSThread sleepForTimeInterval:1];  
  5.     NSLog(@"group1");  
  6. });  
  7. dispatch_group_async(group, queue, ^{  
  8.     [NSThread sleepForTimeInterval:2];  
  9.     NSLog(@"group2");  
  10. });  
  11. dispatch_group_async(group, queue, ^{  
  12.     [NSThread sleepForTimeInterval:3];  
  13.     NSLog(@"group3");  
  14. });  
  15. dispatch_group_notify(group, dispatch_get_main_queue(), ^{  
  16.     NSLog(@"updateUi");  
  17. });  
  18. dispatch_release(group);  

dispatch_group_async是异步的方法,运行后可以看到打印结果:


2012-09-25 16:04:16.737 gcdTest[43328:11303] group1
2012-09-25 16:04:17.738 gcdTest[43328:12a1b] group2
2012-09-25 16:04:18.738 gcdTest[43328:13003] group3
2012-09-25 16:04:18.739 gcdTest[43328:f803] updateUi

每个一秒打印一个,当第三个任务执行后,upadteUi被打印。


3dispatch_barrier_async的使用

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

例子代码如下:


[cpp] view plaincopy


  1. dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT);  
  2. dispatch_async(queue, ^{  
  3.     [NSThread sleepForTimeInterval:2];  
  4.     NSLog(@"dispatch_async1");  
  5. });  
  6. dispatch_async(queue, ^{  
  7.     [NSThread sleepForTimeInterval:4];  
  8.     NSLog(@"dispatch_async2");  
  9. });  
  10. dispatch_barrier_async(queue, ^{  
  11.     NSLog(@"dispatch_barrier_async");  
  12.     [NSThread sleepForTimeInterval:4];  
  13.   
  14. });  
  15. dispatch_async(queue, ^{  
  16.     [NSThread sleepForTimeInterval:1];  
  17.     NSLog(@"dispatch_async3");  
  18. });  


打印结果:


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

请注意执行的时间,可以看到执行的顺序如上所述。

4dispatch_apply 

执行某个代码片段N次。
dispatch_apply(5, globalQ, ^(size_t index) {
    // 执行5
});


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值