分享

iOS多线程开发 NSThread

 杰出天下 2012-05-18

   等待20秒:  [NSThread sleepForTimeInterval:20];


问:

用这两个下载图片,为什么明显能感觉到dispatch慢,代码那里有问题吗?
 

复制代码
- (IBAction) startDownLoad:(id)sender 
{
[activity startAnimating];

//启动线程
// [NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:ImageURL];

dispatch_queue_t newThread = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(newThread, ^{
[self downloadImage:ImageURL];
});
}


//线程函数

- (void) downloadImage:(NSString*)url{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSURL *imageUrl = [[NSURL alloc] initWithString:ImageURL];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:imageUrl];
[imageUrl release];
UIImage *bkImage = [[UIImage alloc] initWithData:imageData];
[imageData release];
self.downloadImage.image = bkImage;
[bkImage release];

[activity stopAnimating];

[pool release];
}
复制代码



 

答:

这个用法确实有问题。 
1、你使用global队列可能会导致分派时间过长。由于global队列中可能会含有较多的系统队列。 
2、dispatch_async调度本身是有开销的,因此你把[activity startAnimating];放在最上面是不妥的。最好的方式是用一个标志,等待download这个函数所处的核被激活后调。 
3、NSThread的方式或许能做更快的切换,因为ARMv6或更高版本的处理器都提供了非常强大的线程切换机制。但是NSThread不会采取多核的分派,因为这个系统接口首先要保证的是用户线程的可靠性。 
而Grand Central Dispatch显式地利用分派队列来做多核分派调度,因此如果是在多核处理器上的话用G_C_D更快。 
如果你的处理器是单核心的话,那么可以使用切换更快的NSThread。 


NSThread是起线程的主角,大部分时候我们使用这个类。

建一个view-based application.

复制代码

在viewcontroller.h中

@interface tNSThreadViewController : UIViewController {

    NSThread *t1;
BOOL bExit;
}
@property BOOL bExit;
- (IBAction)checkit:(id)sender;
- (IBAction)start2:(id)sender;
- (IBAction)start:(id)sender;
-(void)doSomething:(id)data;
@end



在viewcontroller.m中



#import "tNSThreadViewController.h"

@implementation tNSThreadViewController
@synthesize bExit;
-(void)doSomething:(id)data
{

/*[[t1 threadDictionary] setObject:@"kkk" forKey:@"k11"];
NSMutableDictionary * md = [t1 threadDictionary];
NSString *t = [md objectForKey:@"k11"];
NSLog(@"%@", t);
NSLog(@"kasdkf");
*/
//NSString *d = (NSString *)data;
//NSLog(@"%@",d);
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *s = [NSString stringWithFormat:@"%d",1];
NSLog(@"%@",s);
while (true) {
[NSThread sleepForTimeInterval:0.5];
NSLog(@"IN thread");
tNSThreadViewController *inputData = (tNSThreadViewController *)data;
if(inputData.bExit)
break;
}
[pool release];


}


- (void)viewDidLoad
{
bExit = NO;
[super viewDidLoad];
}



- (IBAction)checkit:(id)sender {
bExit = YES;
if([NSThread isMultiThreaded]) {
NSLog(@"it is multi thread");
}
else {
NSLog(@"it is not multi thread");
}
}

- (IBAction)start2:(id)sender {
if(t1 != nil)
{

[t1 release];
t1 = nil;
}
t1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:self];
[t1 setStackSize:1024*1024];
[t1 setThreadPriority:0.5];

[t1 start];

/*NSThread *t2 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:@"2"];
[t2 setStackSize:1024*1024];
[t2 setThreadPriority:0.9];

[t2 start];
*/

}

- (IBAction)start:(id)sender {
//[self performSelectorInBackground:<#(SEL)#> withObject:<#(id)#>
[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];
}
@end

最后需要说明的是,iOS应用的主线程的栈大小缺省为1M,其他线程的栈大小缺省为512K,很多网上文章说,不能调这个大小,那是以讹传讹,如何调大小,请看我的代码。



iOS 支持多个层次的多线程编程,层次越高的抽象程度越高,使用起来也越方便,也是苹果最推荐使用的方法。下面根据抽象层次从低到高依次列出iOS所支持的多线程编程范式:
1, Thread;
2, Cocoa operations;
3, Grand Central Dispatch (GCD) (iOS4 才开始支持)

下面简要说明这三种不同范式:
Thread 是这三种范式里面相对轻量级的,但也是使用起来最负责的,你需要自己管理thread的生命周期,线程之间的同步。线程共享同一应用程序的部分内存空间,它们拥有对数据相同的访问权限。你得协调多个线程对同一数据的访问,一般做法是在访问之前加锁,这会导致一定的性能开销。在 iOS 中我们可以使用多种形式的 thread:
Cocoa threads: 使用NSThread 或直接从 NSObject 的类方法 performSelectorInBackground:withObject: 来创建一个线程。如果你选择thread来实现多线程,那么 NSThread 就是官方推荐优先选用的方式。
POSIX threads: 基于 C 语言的一个多线程库,

Cocoa operations是基于 Obective-C实现的,类 NSOperation 以面向对象的方式封装了用户需要执行的操作,我们只要聚焦于我们需要做的事情,而不必太操心线程的管理,同步等事情,因为NSOperation已经为我们封装了这些事情。 NSOperation 是一个抽象基类,我们必须使用它的子类。iOS 提供了两种默认实现:NSInvocationOperation 和 NSBlockOperation。

Grand Central Dispatch (GCD): iOS4 才开始支持,它提供了一些新的特性,以及运行库来支持多核并行编程,它的关注点更高:如何在多个 cpu 上提升效率。

有了上面的总体框架,我们就能清楚地知道不同方式所处的层次以及可能的效率,便利性差异。下面我们先来看看 NSThread 的使用,包括创建,启动,同步,通信等相关知识。这些与 win32/Java 下的 thread 使用非常相似。

线程创建与启动
NSThread的创建主要有两种直接方式:
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];

NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                        selector:@selector(myThreadMainMethod:)
                                        object:nil];
[myThread start];

这两种方式的区别是:前一种一调用就会立即创建一个线程来做事情;而后一种虽然你 alloc 了也 init了,但是要直到我们手动调用 start 启动线程时才会真正去创建线程。这种延迟实现思想在很多跟资源相关的地方都有用到。后一种方式我们还可以在启动线程之前,对线程进行配置,比如设置 stack 大小,线程优先级。

还有一种间接的方式,更加方便,我们甚至不需要显式编写 NSThread 相关代码。那就是利用 NSObject 的类方法 performSelectorInBackground:withObject: 来创建一个线程:
[myObj performSelectorInBackground:@selector(myThreadMainMethod) withObject:nil];
其效果与 NSThread 的 detachNewThreadSelector:toTarget:withObject: 是一样的。

 

线程同步
线程的同步方法跟其他系统下类似,我们可以用原子操作,可以用 mutex,lock等。
iOS的原子操作函数是以 OSAtomic开头的,比如:OSAtomicAdd32, OSAtomicOr32等等。这些函数可以直接使用,因为它们是原子操作。

iOS中的 mutex 对应的是 NSLock,它遵循 NSLooking协议,我们可以使用 lock, tryLock, lockBeforeData:来加锁,用 unLock来解锁。使用示例:

复制代码
BOOL moreToDo = YES;
NSLock *theLock = [[NSLock alloc] init];
...
while (moreToDo) {
/* Do another increment of calculation */
/* until there’s no more to do. */
if ([theLock tryLock]) {
/* Update display used by all threads. */
[theLock unlock];
}
}
复制代码

我们可以使用指令 @synchronized 来简化 NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。

复制代码
- (void)myMethod:(id)anObj
{
@synchronized(anObj)
{
// Everything between the braces is protected by the @synchronized directive.
}
}
复制代码

还有其他的一些锁对象,比如:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,在这里就不一一介绍了,大家去看官方文档吧。


NSCodition同步执行的顺序
NSCodition 是一种特殊类型的锁,我们可以用它来同步操作执行的顺序。它与 mutex 的区别在于更加精准,等待某个 NSCondtion 的线程一直被 lock,直到其他线程给那个 condition 发送了信号。下面我们来看使用示例:

某个线程等待着事情去做,而有没有事情做是由其他线程通知它的。

复制代码
[cocoaCondition lock];
while (timeToDoWork <= 0)
[cocoaCondition wait];

timeToDoWork--;
// Do real work here.
[cocoaCondition unlock];
复制代码

其他线程发送信号通知上面的线程可以做事情了:

[cocoaCondition lock];
timeToDoWork++;
[cocoaCondition signal];
[cocoaCondition unlock];


复制代码
线程间通信
线程在运行过程中,可能需要与其它线程进行通信。我们可以使用 NSObject 中的一些方法:
在应用程序主线程中做事情:
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:

在指定线程中做事情:
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:

在当前线程中做事情:
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:

取消发送给当前线程的某个消息
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

如在我们在某个线程中下载数据,下载完成之后要通知主线程中更新界面等等,可以使用如下接口:- (void)myThreadMainMethod
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// to do something in your thread job
...
[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO];
[pool release];
}
复制代码

 

RunLoop
说到 NSThread 就不能不说起与之关系相当紧密的 NSRunLoop。Run loop 相当于 win32 里面的消息循环机制,它可以让你根据事件/消息(鼠标消息,键盘消息,计时器消息等)来调度线程是忙碌还是闲置。
系统会自动为应用程序的主线程生成一个与之对应的 run loop 来处理其消息循环。在触摸 UIView 时之所以能够激发 touchesBegan/touchesMoved 等等函数被调用,就是因为应用程序的主线程在 UIApplicationMain 里面有这样一个 run loop 在分发 input 或 timer 事件。

1.什么是NSRunLoop
我们会经常看到这样的代码:

复制代码
- (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil];
[progress setHidden:NO];
while (pageStillLoading) {
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[progress setHidden:YES];
}
复制代码

这段代码很神奇的,因为他会“暂停”代码运行,而且程序运行不会因为这里有一个while循环而受到影响。在[progress setHidden:NO]执行之后,整个函数想暂停了一样停在循环里面,等loadPageInBackground里面的操作都完成了以后才让[progress setHidden:YES]运行。这样做就显得简介,而且逻辑很清晰。如果你不这样做,你就需要在loadPageInBackground里面表示load完成的地方调用[progress setHidden:YES],显得代码不紧凑而且容易出错。
那么具体什么是NSRunLoop呢?其实NSRunLoop的本质是一个消息机制的处理模式。如果你对vc++编程有一定了解,在windows中,有一系列很重要的函数SendMessage,PostMessage,GetMessage,这些都是有关消息传递处理的API。但是在你进入到Cocoa的编程世界里面,我不知道你是不是走的太快太匆忙而忽视了这个很重要的问题,Cocoa里面就没有提及到任何关于消息处理的API,开发者从来也没有自己去关心过消息的传递过程,好像一切都是那么自然,像大自然一样自然?在Cocoa里面你再也不用去自己定义WM_COMMAD_XXX这样的宏来标识某个消息,也不用在switch-case里面去对特定的消息做特别的处理。难道是Cocoa里面就没有了消息机制?答案是否定的,只是Apple在设计消息处理的时候采用了一个更加高明的模式,那就是RunLoop。

2. NSRunLoop工作原理
接下来看一下NSRunLoop具体的工作原理,首先是官方文档提供的说法,看图:

通过所有的“消息”都被添加到了NSRunLoop中去,而在这里这些消息并分为“input source”和“Timer source” 并在循环中检查是不是有事件需要发生,如果需要那么就调用相应的函数处理。为了更清晰的解释,我们来对比VC++和iOS消息处理过程。

VC++中在一切初始化都完成之后程序就开始这样一个循环了(代码是从户sir mfc程序设计课程的slides中截取):

复制代码
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR  lpCmdLine,int nCmdShow){
...
while (GetMessage(&msg, NULL, 0, 0)){
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
复制代码

可以看到在GetMessage之后就去分发处理消息了,而iOS中main函数中只是调用了UIApplicationMain,那么我们可以介意猜出UIApplicationMain在初始化完成之后就会进入这样一个情形:

复制代码
int UIApplicationMain(...){
...
while(running){
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
...
}
复制代码

所以在UIApplicationMain中也是同样在不断处理runloop才是的程序没有退出。刚才的我说了NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和封装,这样才能是的你不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每一个消息就被打包在input source或者是timer source中了,当需要处理的时候就直接调用其中包含的相应对象的处理函数了。所以对外部的开发人员来讲,你感受到的就是,把source/timer加入到runloop中,然后在适当的时候类似于[receiver action]这样的事情发生了。甚至很多时候,你都没有感受到整个过程前半部分,你只是感觉到了你的某个对象的某个函数调用了。比如在UIView被触摸时会用touchesBegan/touchesMoved等等函数被调用,也许你会想,“该死的,我都不知道在那里被告知有触摸消息,这些处理函数就被调用了!?”所以,消息是有的,只是runloop已经帮你做了!为了证明我的观点,我截取了一张debug touchesBegan的call stack,有图有真相:

 

 

iPhone开发应用中NSOperation多线程使用是本文要介绍的内容,首先创建一个线程类,RequestOperation,它继承NSOperation,而后我们在控制器类当中,创建一个NSOperationQueue对象,将该线成加入到序列中  。它就会自动的从NSOperationQueue当中取到我们加入的线程,而后运行线成的start方法  。

复制代码
1.    #import "RootViewController.h"  
2. @implementation RootViewController
3. #pragma mark -
4. #pragma mark View lifecycle
5. -(void)buttonClicked:(id)sender{
6. _queue=[[NSOperationQueue alloc] init];
7. //第一个请求
8. NSURLRequest *request=[NSURLRequest requestWithURL:[NSURL URLWithString:@"http:www.google.com"]];
9. RequestOperation *operation=[[RequestOperation alloc] initWithRequest:request];
10. [_queue addOperation:operation];
11. [operation release];
12. //第二个请求
13. //NSURLRequest *request2=[NSURLRequest requestWithURL:[NSURL URLWithString:@"http:www.baidu.com"]];
14. //RequestOperation *operation1=[[RequestOperation alloc]initWithRequest:request2];
15. //operation1.message=@"operation1---";
16. //[_queue addOperation:operation1];
17. }
18. #import <Foundation/Foundation.h>
19. @interface RequestOperation : NSOperation{
20. NSURLRequest *_request;
21. NSMutableData *_data;
22. NSString *message;
23. }
24. @property(nonatomic,retain)NSString *message;
25. -(id)initWithRequest:(NSURLRequest*)request;
26. @end
27.
28. //
29. // RequestOperation.m
30. // NSOperation
31. //
32. // Created by wangqiulei on 8/23/10.
33. // Copyright 2010 __MyCompanyName__. All rights reserved.
34. //
35. #import "RequestOperation.h"
36. @implementation RequestOperation
37. @synthesize message;
38. -(id)initWithRequest:(NSURLRequest *)request{
39.
40. if (self=[self init]) {
41. _request=[request retain];
42. _data=[[NSMutableData data]retain];
43. }
44. return self;
45. }
46. -(void)dealloc{
47. [_request release];
48. [_data release];
49. [super dealloc];
50. }
51. //如果返回为YES表示asychronously方式处理
52. -(BOOL)isConcurrent{
53.
54. return YES;
55. }
56. //开始处理
57. -(void)start{
58. if (![self isCancelled]) {
59.
60. NSLog(@"%@",self.message);
61. NSLog(@"-------------%d",[self retainCount]);
62. [NSURLConnection connectionWithRequest:_request delegate:self];
63. }
64. }
65. //取得数据
66. -(void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
67. //添加数据
68. [_data appendData:data];
69. NSLog(@"%@",_data);
70. }
71. //http请求结束
72. -(void)connectionDidFinishLoading:(NSURLConnection *)connection{
73. }
74. @end
复制代码



 

在iphone开发中经常需要更新UI的显示,一般需要启动新的线程来运行相关数据地更新,然后在另一线程中更新UI. 这里利用NSThread实现这样一个功能:更新进度条。

复制代码
//
// NSThreadDemoAppDelegate.m
// NSThreadDemo
//
// Created by Chelsea Wang(420989762/wwssttt@163.com) on 11-10-11.
// Copyright 2011年 __MyCompanyName__. All rights reserved.
//


@implementation NSThreadDemoAppDelegate
float processValue = 0;
@synthesize window = _window;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
UIProgressView* processView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
[processView setFrame:CGRectMake(10, 50, 200, 30)];
[processView setTag:101];
[processView setProgress:0.0];

UILabel* processLabel = [[UILabel alloc] initWithFrame:CGRectMake(225, 30, 100, 50)];
[processLabel setText:[NSString stringWithFormat:@"%.2f%%",processValue*100]];
[processLabel setTag:102];



[self.window addSubview:processView];
[processView release];
[self.window addSubview:processLabel];
[processLabel release];
[self.window makeKeyAndVisible];

[NSThread detachNewThreadSelector:@selector(updateProcess) toTarget:self withObject:nil];

return YES;
}

-(void)updateProcess{
NSAutoreleasePool* p = [[NSAutoreleasePool alloc] init];
[self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:YES];
[p release];
}
-(void)updateUI{
if (processValue <= 1.0) {
processValue += 0.1;

UIProgressView* processView = (UIProgressView*)[self.window viewWithTag:101];
[processView setProgress:processValue];

UILabel* processLabel = (UILabel*)[self.window viewWithTag:102];
[processLabel setText:[NSString stringWithFormat:@"%.2f%%",processValue*100]];

[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateUI) userInfo:nil repeats:NO];
}else{
processValue = 0.0;
[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateUI) userInfo:nil repeats:NO];
}
}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多