iOS多线程编程技术之NSThread、Cocoa NSOperation、GCD

简介
iOS有三种多线程编程的技术,分别是:
(一)NSThread
(二)Cocoa NSOperation
(三)GCD(全称:Grand Central Dispatch)java

这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的。程序员

 

三种方式的优缺点介绍:
1)NSThread:
优势:NSThread 比其余两个轻量级
缺点:须要本身管理线程的生命周期,线程同步。线程同步对数据的加锁会有必定的系统开销数据库

NSThread实现的技术有下面三种:
~MA59HJ2_9MN7%]4Y%}FDGW
通常使用cocoa thread 技术。编程

Cocoa NSOperation
优势:不须要关心线程管理,数据同步的事情,能够把精力放在本身须要执行的操做上。
Cocoa operation 相关的类是 NSOperation ,NSOperationQueue。
NSOperation是个抽象类,使用它必须用它的子类,能够实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。
建立NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。网络

GCD
Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。在iOS4.0开始以后才能使用。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。如今的iOS系统都升级到7了,因此不用担忧该技术不能使用。多线程

介绍完这三种多线程编程方式,本文将依次介绍这三种技术的使用。并发

(一)NSThread的使用
NSThread 有两种直接建立方式:app

1
2
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument
+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument

第一个是实例方法,第二个是类方法框架

1
2
3
4
5
6
一、[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];  
 
二、NSThread* myThread = [[NSThread alloc] initWithTarget:self  
                                         selector:@selector(doSomething:)  
                                         object:nil];  
[myThread start];

参数的意义:
selector :线程执行的方法,这个selector只能有一个参数,并且不能有返回值。
target  :selector消息发送的对象
argument:传输给target的惟一参数,也能够是nil异步

第一种方式会直接建立线程而且开始运行线程,第二种方式是先建立线程对象,而后再运行线程操做,在运行线程操做前能够设置线程的优先级等线程信息

不显式建立线程的方法:
用NSObject的类方法  performSelectorInBackground:withObject: 建立一个线程:

1
[Obj performSelectorInBackground:@selector(doSomething) withObject:nil];

下载图片的例子:
新建singeView app
新建项目,并在xib文件上放置一个imageView控件。按住control键拖到viewController.h文件中建立imageView IBOutlet ViewController.m中实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
//  
//  ViewController.m  
//  NSThreadDemo  
//  
//  Created by rongfzh on 12-9-23.  
//  Copyright (c) 2012年 rongfzh. All rights reserved.  
//  
 
#import "ViewController.h"  
#define kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"  
@interface ViewController ()  
 
@end  
 
@implementation ViewController  
 
-(void)downloadImage:(NSString *) url{  
     NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]];  
     UIImage *image = [[UIImage alloc]initWithData:data];  
     if(image == nil){  
 
     }else{  
         [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];  
     }  
}  
 
-(void)updateUI:(UIImage*) image{  
     self.imageView.image = image;  
}  
 
- (void)viewDidLoad  
{  
     [super viewDidLoad];  
 
//    [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL];  
     NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL];  
     [thread start];  
}  
 
- (void)didReceiveMemoryWarning  
{  
     [super didReceiveMemoryWarning];  
     // Dispose of any resources that can be recreated.  
}  
 
@end

线程间通信
线程下载完图片后怎么通知主线程更新界面呢?

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

performSelectorOnMainThread是NSObject的方法,除了能够更新主线程的数据外,还能够更新其余线程的好比:

1
performSelector:onThread:withObject:waitUntilDone:

运行下载图片:
VFE8W}]XP~E6K0RPJK10EIU

线程同步
咱们演示一个经典的卖票的例子来说NSThread的线程同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import <UIKit/UIKit.h>  
 
@class ViewController;  
 
@interface AppDelegate : UIResponder <UIApplicationDelegate>  
{  
     int tickets;  
     int count;  
     NSThread* ticketsThreadone;  
     NSThread* ticketsThreadtwo;  
     NSCondition* ticketsCondition;  
     NSLock *theLock;  
}  
@property (strong, nonatomic) UIWindow *window;  
 
@property (strong, nonatomic) ViewController *viewController;  
 
@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions  
{  
 
     tickets = 100;  
     count = 0;  
     theLock = [[NSLock alloc] init];  
     // 锁对象  
     ticketsCondition = [[NSCondition alloc] init];  
     ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];  
     [ticketsThreadone setName:@"Thread-1"];  
     [ticketsThreadone start];  
 
     ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];  
     [ticketsThreadtwo setName:@"Thread-2"];  
     [ticketsThreadtwo start];  
 
     self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];  
     // Override point for customization after application launch.  
     self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];  
     self.window.rootViewController = self.viewController;  
     [self.window makeKeyAndVisible];  
     return YES;  
}  
 
- (void)run{  
     while (TRUE) {  
         // 上锁  
//        [ticketsCondition lock];  
         [theLock lock];  
         if(tickets >= 0){  
             [NSThread sleepForTimeInterval:0.09];  
             count = 100 - tickets;  
             NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);  
             tickets--;  
         }else{  
             break;  
         }  
         [theLock unlock];  
//        [ticketsCondition unlock];  
     }  
}

若是没有线程同步的lock,卖票数多是-1.加上lock以后线程同步保证了数据的正确性。

上面例子我使用了两种锁,一种NSCondition ,一种是:NSLock。 NSCondition我已经注释了。

线程的顺序执行
他们均可以经过[ticketsCondition signal]; 发送信号的方式,在一个线程唤醒另一个线程的等待。

好比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#import "AppDelegate.h"  
 
#import "ViewController.h"  
 
@implementation AppDelegate  
 
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions  
{  
 
     tickets = 100;  
     count = 0;  
     theLock = [[NSLock alloc] init];  
     // 锁对象  
     ticketsCondition = [[NSCondition alloc] init];  
     ticketsThreadone = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];  
     [ticketsThreadone setName:@"Thread-1"];  
     [ticketsThreadone start];  
 
     ticketsThreadtwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];  
     [ticketsThreadtwo setName:@"Thread-2"];  
     [ticketsThreadtwo start];  
 
     NSThread *ticketsThreadthree = [[NSThread alloc] initWithTarget:self selector:@selector(run3) object:nil];  
     [ticketsThreadthree setName:@"Thread-3"];  
     [ticketsThreadthree start];      
     self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];  
     // Override point for customization after application launch.  
     self.viewController = [[ViewController alloc] initWithNibName:@"ViewController" bundle:nil];  
     self.window.rootViewController = self.viewController;  
     [self.window makeKeyAndVisible];  
     return YES;  
}  
 
-(void)run3{  
     while (YES) {  
         [ticketsCondition lock];  
         [NSThread sleepForTimeInterval:3];  
         [ticketsCondition signal];  
         [ticketsCondition unlock];  
     }  
}  
 
- (void)run{  
     while (TRUE) {  
         // 上锁  
         [ticketsCondition lock];  
         [ticketsCondition wait];  
         [theLock lock];  
         if(tickets >= 0){  
             [NSThread sleepForTimeInterval:0.09];  
             count = 100 - tickets;  
             NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);  
             tickets--;  
         }else{  
             break;  
         }  
         [theLock unlock];  
         [ticketsCondition unlock];  
     }  
}

wait是等待,我加了一个 线程3 去唤醒其余两个线程锁中的wait。

其余同步
咱们可使用指令 @synchronized 来简化 NSLock的使用,这样咱们就没必要显示编写建立NSLock,加锁并解锁相关代码。

1
2
3
4
5
6
7
- (void)doSomeThing:(id)anObj
{
     @synchronized(anObj)
     {
         // Everything between the braces is protected by the @synchronized directive.
     }
}

还有其余的一些锁对象,好比:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,能够本身看官方文档学习

NSThread下载图片的例子代码:http://download.csdn.net/detail/totogo2010/4591149

(二)Cocoa NSOperation的使用
使用 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界面。
实现代码以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#import "ViewController.h"  
#define kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"  
 
@interface ViewController ()  
 
@end  
 
@implementation ViewController  
 
- (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;  
}

代码注释:
1.viewDidLoad方法里能够看到咱们用NSInvocationOperation建了一个后台线程,而且放到2.NSOperationQueue中。后台线程执行downloadImage方法。
3.downloadImage 方法处理下载图片的逻辑。下载完成后用performSelectorOnMainThread执行主线程updateUI方法。
updateUI 并把下载的图片显示到图片控件中。

运行能够看到下载图片显示在界面上。
thjyjytktfb464dfgbdgw4

第二种方式继承NSOperation 
在.m文件中实现main方法,main方法编写要执行的代码便可。

如何控制线程池中的线程数?
队列里能够加入不少个NSOperation, 能够把NSOperationQueue看做一个线程池,可往线程池中添加操做(NSOperation)到队列中。线程池中的线程可看做消费者,从队列中取走操做,并执行它。

经过下面的代码设置:

1
[queue setMaxConcurrentOperationCount:5];

线程池中的线程数,也就是并发操做数。默认状况下是-1,-1表示没有限制,这样会同时运行队列中的所有的操做。

(三)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如何使用?

一、经常使用的方法dispatch_async
为了不界面在处理耗时的操做时卡死,好比读取网络数据,IO,数据库读写等,咱们会在另一个线程中处理这些操做,而后通知主线程更新界面。

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

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

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

1
2
3
4
5
6
7
8
9
10
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
     NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"];  
     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;  
          });  
     }  
});

运行显示:
fdgrehrthjytjasf4535gfn

是否是代码比NSThread  NSOperation简洁不少,并且GCD会自动根据任务在多核处理器上分配资源,优化程序。

系统给每个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不一样。由于是全局的,咱们不须要去建立。咱们只须要经过使用函数dispath_get_global_queue去获得队列,以下:

1
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

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

1
dispatch_queue_t mainQ = dispatch_get_main_queue();

虽然dispatch queue是引用计数的对象,可是以上两个都是全局的队列,不用retain或release。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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_group_async是异步的方法,运行后能够看到打印结果:

1
2
3
4
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被打印。

三、dispatch_barrier_async的使用
dispatch_barrier_async是在前面的任务执行结束后它才执行,并且它后面的任务等它执行完成以后才会执行
例子代码以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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");  
});

打印结果:

1
2
3
4
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次。

1
2
3
dispatch_apply(5, globalQ, ^(size_t index) {
     // 执行5次
});
相关文章
相关标签/搜索