iOS多线程编程技术NSThread、Cocoa NSOperation、GCD三者使用详解

时间:2021-10-26 18:27:23
简介
iOS有三种多线程编程的技术,分别是:
(一)NSThread

(二)Cocoa NSOperation

(三)GCD(全称:Grand Central Dispatch)

三种方式的优缺点介绍:
1)NSThread

优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销

2)Cocoa  NSOperation
优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。
Cocoa operation相关的类是NSOperation, NSOperationQueue.
NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类: NSInvocationOperation和NSBlockOperation.
创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。

3) GCD (全优点)

Grand Central dispatch(GCD)是Apple开发的一个多核编程的解决方案。在iOS4.0开始之后才能使用。GCD是一个替代NSThread, NSOperationQueue,NSInvocationOperation等技术的很高效强大的技术。


下面我用简单易于理解的代码实例来介绍NSThread在开发实践中的使用,具体使用时可以根据实际情况进行扩展:
一、NSThread的使用(基本已过时)

#import <UIKit/UIKit.h>

#define kURL @"http://www.iyi8.com/uploadfile/2014/0506/20140506085929652.jpg"

@interface ViewController : UIViewController

@end

#import "ViewController.h"

@interface ViewController ()
{
UIImage *_image;
UIImageView *_imageView;

int _tickets;
int _count;

NSThread *_threadOne;
NSThread *_threadTwo;
NSThread *_threadThree;

NSCondition *_condition;

NSLock *_lock;
}
@end

@implementation ViewController

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
}
return self;
}

- (void)loadView
{
self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)];
NSLog(@"klfaklfa ------ - - ");

_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 150)];
[self.view addSubview:_imageView];
}

/** 测试NSthread使用*/
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    _tickets    = 200;
    _count      = 0;
    _lock       = [[NSLock alloc] init];
    
    //锁对象
    _condition = [[NSCondition alloc] init];
    //线程1
    _threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    _threadOne.name = @"thread-1";
    [_threadOne start];
    
    //线程2
    _threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    _threadTwo.name = @"thread-2";
    [_threadTwo start];
    
    //线程3
    _threadThree = [[NSThread alloc] initWithTarget:self selector:@selector(run3) object:nil];
    _threadThree.name = @"thread-3";
    [_threadThree start];
    
    //如果没有线程同步的lock,物品售出数量就会出现重复导致数据竞争不同步问题.加上lock之后线程同步保证了数据的正确性。
    [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage:) object:kURL];
    [thread start];
}   

/** 测试NSOperationQueue使用

使用 NSOperation的方式有两种, 一种是用定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。 另一种是继承NSOperation   如果你也熟悉Java,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一样,NSOperation也是设计用来扩展的,只需继承重写NSOperation的一个方法main。相当与java 中Runnalbe的Run方法。然后把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。   NSInvocationOperation例子: 这里同样,我们实现一个下载图片的例子。新建一个Single View app,拖放一个ImageView控件到xib界面。实现代码如下

*/

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
    selector:@selector(downloadImage:)
     object:kURL];
     
     NSOperationQueue *queue = [[NSOperationQueue alloc] init];
     [queue addOperation:operation];
}   

第二种方式继承NSOperation  在.m文件中实现main方法,main方法编写要执行的代码即可。   如何控制线程池中的线程数? 队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。   通过下面的代码设置:
 
 
  1. [queue setMaxConcurrentOperationCount:5]; 
  线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部的操作。  

/** 测试GCD的dispatch_async使用*/

- (void)viewDidLoad
{
    [super viewDidLoad];
    
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     NSURL * url = [NSURL URLWithString:<span>kURL</span>];
     NSData * data = [[NSData alloc]initWithContentsOfURL:url];
     UIImage *image = [[UIImage alloc]initWithData:data];
        
         if (data != nil) {
             dispatch_async(dispatch_get_main_queue(), ^{
             _imageView.image = image;
            });
         }
     });
}


(三)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 queue与Serial queue之间是并发执行的。   Concurrent 又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。   Main dispatch queue 它是全局可用的serial queue,它是在应用程序主线程上执行任务的。   我们看看dispatch queue如何使用?   1、常用的方法dispatch_async 为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。  用GCD实现这个流程的操作比前面介绍的NSThread  NSOperation的方法都要简单。代码框架结构如下

/** 测试GCD的dispatch_group_async使用*/

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //此方法可以实现监听一组任务是否完成,如果完成后通知其他操作(如界面更新),此方法在下载附件时挺有用,
     //在搪行几个下载任务时,当下载完成后通过dispatch_group_notify通知主线程下载完成并更新相应界面
     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:0.09];
     
     NSLog(@"group1");
     NSURL * url = [NSURL URLWithString:kURL];
     NSData * data = [[NSData alloc]initWithContentsOfURL:url];
     _image = [[UIImage alloc]initWithData:data];
     
     });
     dispatch_group_async(group, queue, ^{
         [NSThread sleepForTimeInterval:0.09];
         NSLog(@"group2");
     });
     dispatch_group_async(group, queue, ^{
         [NSThread sleepForTimeInterval:0.09];
         NSLog(@"group3");
     });
     
     dispatch_group_notify(group, dispatch_get_main_queue(), ^{
         NSLog(@"updateUi");
     
         _imageView.image = _image;
     });
}

/** 测试GCD的dispatch_barrier_async使用*/

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    //是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
    dispatch_queue_t queue = dispatch_queue_create("gcd.devdiy.com", 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_async");
    });
}

- (void)run3
{
while (true) {
[_condition lock];
[NSThread sleepForTimeInterval:3];
NSLog(@"当前物品名称:%d,售出数量:%d,线程名-=-==: %@", _tickets, _count, [[NSThread currentThread] name]);

[_condition signal];
[_condition unlock];
}
}

- (void)run
{
while (true) {
//上锁
[_lock lock];
if (_tickets > 0) {
[NSThread sleepForTimeInterval:0.09];
_count = 200 - _tickets;
NSLog(@"当前物品名称:%d,售出数量:%d,线程名: %@", _tickets, _count, [[NSThread currentThread] name]);
_tickets--;
}else{
break;
}

[_lock unlock];
}
}

/**
* @param string url
*/
- (void)downloadImage:(NSString *) url{
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];
UIImage *image = [[UIImage alloc] initWithData:data];
if(image == nil){
[self updateUI:image];
}else{
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
}
}

- (void)updateUI:(UIImage *)image
{
_imageView.image = image;
}

@end

是不是代码比NSThread  NSOperation简洁很多,而且GCD会自动根据任务在多核处理器上分配资源,优化程序。   系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列。

以上代码组织的不是很好,只简单描述了一下多线程的基本概念,后续我再添加详细应用的实例,现只做为参考,在具体实际应用中还得我们自己来进行编写发挥。希望对大家有所帮助和启发。