多线程简介

在开发过程中应该尽可能减少用户等待时间,让程序尽可能快的完成运算。可是无论是哪种语言开发的程序最终往往转换成汇编语言进而解释成机器码来执行。但是机器码是按顺序执行的,一个复杂的多步操作只能一步步按顺序逐个执行。改变这种状况可以从两个角度出发:对于单核处理器,可以将多个步骤放到不同的线程,这样一来用户完成UI操作后其他后续任务在其他线程中,当CPU空闲时会继续执行,而此时对于用户而言可以继续进行其他操作;对于多核处理器,如果用户在UI线程中完成某个操作之后,其他后续操作在别的线程中继续执行,用户同样可以继续进行其他UI操作,与此同时前一个操作的后续任务可以分散到多个空闲CPU中继续执行(当然具体调度顺序要根据程序设计而定),及解决了线程阻塞又提高了运行效率。

NSThread

轻量级,需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销

//类方法 
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];  
//实例方法  
NSThread* myThread = [[NSThread alloc] initWithTarget:self                    selector:@selector(doSomething:)   object:nil]; 
[myThread start];

线程间通讯,子线程下载完图片通知更新主线程:

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

还可以更新其他线程:

performSelector:onThread:withObject:waitUntilDone:

CocoaNSOperation

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

NSInvocationOperation下载图片实例:
- (void)viewDidLoad   
{   
    [super viewDidLoad];   
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL];   
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];   
    [queue addOperation:operation];   
    // Do any additional setup after loading the view, typically from a nib.   
}   
-(void)downloadImage:(NSString *)url{   
    NSLog(@"url:%@", url);   
    NSURL *nsUrl = [NSURL URLWithString:url];   
    NSData *data = [[NSData alloc]initWithContentsOfURL:nsUrl];   
    UIImage * image = [[UIImage alloc]initWithData:data];   
    [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];   
}   
-(void)updateUI:(UIImage*) image{   
    self.imageView.image = image;   
}

下载完成后用performSelectorOnMainThread执行主线程updateUI方法

NSBlockOperation创建多个线程加载图片
#pragma mark 多线程下载图片
-(void)loadImageWithMultiThread{
    int count=ROW_COUNT*COLUMN_COUNT;
    //创建操作队列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    operationQueue.maxConcurrentOperationCount=5;//设置最大并发线程数
    //创建多个线程用于填充图片
    for (int i=0; i<count; ++i) {
        //方法:直接使用操队列添加操作
        [operationQueue addOperationWithBlock:^{
            [self loadImage:[NSNumber numberWithInt:i]];//操作UI
        }];
   }
}

继承NSOperation实现多线程操作:
线程NSOperation被加入到线程队列NSOperationQueue中执行,可以控制线程队列中线程的数量,默认是-1:

[queue setMaxConcurrentOperationCount:5];

GCD (Grand Central Dispatch)

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始之后才能使用。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术,用于优化应用程序以支持多核心处理器和其他的对称多处理系统。
GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行。
dispatch queue分为下面三种:

Serial串行队列

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

Concurrent 并发队列

又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。系统提供了三个优先级不同的concurrent dispatch queues:

dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

Main dispatch queue 主队列

它是全局可用的serial queue,它是在应用程序主线程上执行任务的。
主线程用来操作界面相关的任务:

dispatch_queue_t mainQ = dispatch_get_main_queue();

GCD多线程操作的常用方法如下:

dispatch_async 异步调用

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{   
    NSURL * url = [NSURL URLWithString:@""];   
    NSData * data = [[NSData alloc]initWithContentsOfURL:url];   
    UIImage *image = [[UIImage alloc]initWithData:data];   
    if (data != nil) {   
        dispatch_async(dispatch_get_main_queue(), ^{   
            self.imageView.image = image;   
         });   
    }   
});

dispatch_group_async

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);

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");   
});

dispatch_apply

执行某个代码片段N次。

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

dispatch_once():单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。
dispatch_time():延迟一定的时间后执行。

线程同步

说到多线程就不得不提多线程中的锁机制,多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题。比如影院线上订票系统,如果有100个人在购票,而总票数只剩下80张,而且这100个人都已经进入了购票的环节,要解决资源抢夺问题,iOS中有常用的两种方法,NSLock同步锁和@synchronized代码块。

NSLock

#pragma mark 请求图片数据
-(NSData *)requestData:(int )index{
    NSData *data;
    NSString *name;
    //加锁
    [_lock lock];
    if (_imageNames.count>0) {
        name=[_imageNames lastObject];
        [_imageNames removeObject:name];
    }
    //使用完解锁
    [_lock unlock];
    if(name){
        NSURL *url=[NSURL URLWithString:name];
        data=[NSData dataWithContentsOfURL:url];
    }
    return data;
}

@synchronized代码块

-(NSData *)requestData:(int )index{
    NSData *data;
    NSString *name;
    //线程同步
    @synchronized(self){
        if (_imageNames.count>0) {
            name=[_imageNames lastObject];
            [NSThread sleepForTimeInterval:0.001f];
            [_imageNames removeObject:name];
        }
    }
    if(name){
        NSURL *url=[NSURL URLWithString:name];
        data=[NSData dataWithContentsOfURL:url];
    }
    return data;
}