线程 NSCondition NSThread

来源:互联网 发布:服务器编程 编辑:程序博客网 时间:2024/05/19 09:03

多线程在各种编程语言中都是难点,很多语言中实现起来很麻烦,objective-c虽然源于c,但其多线程编程却相当简单,可以与java相媲美。这篇文章主要从线程创建与启动、线程的同步与锁、线程的交互、线程池等等四个方面简单的讲解一下iphone中的多线程编程。

一、线程创建与启动

线程创建主要有三种方式:

- (id)init; // designated initializer- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;

这两种方法创建后,需要手机启动,启动的方法是:

- (void)start;

当然,还有一种比较特殊,就是使用所谓的convenient method,这个方法可以直接生成一个线程并启动它,而且无需为线程的清理负责。这个方法的接口是:

+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument

还有利用 NSObject 的类方法 performSelectorInBackground:withObject: 来创建一个线程:

[myObj performSelectorInBackground:@selector(myThreadMainMethod) withObject:nil];         

二、线程的同步与锁
要说明线程的同步与锁,最好的例子可能就是多个窗口同时售票的售票系统了。我们知道在java中,使用synchronized来同步,而iphone虽然没有提供类似java下的synchronized关键字,但提供了NSCondition对象接口。查看NSCondition的接口说明可以看出,NSCondition是iphone下的锁对象,所以我们可以使用NSCondition实现iphone中的线程安全。这是来源于网上的一个例子:
SellTicketsAppDelegate.h 文件

// SellTicketsAppDelegate.h#import <UIKit/UIKit.h> @interface SellTicketsAppDelegate : NSObject <UIApplicationDelegate> {int tickets;int count;NSThread* ticketsThreadone;NSThread* ticketsThreadtwo;NSCondition* ticketsCondition;UIWindow *window;}@property (nonatomic, retain) IBOutlet UIWindow *window;@end

SellTicketsAppDelegate.m 文件

// SellTicketsAppDelegate.m#import "SellTicketsAppDelegate.h" @implementation SellTicketsAppDelegate@synthesize window; - (void)applicationDidFinishLaunching:(UIApplication *)application {tickets = 100;count = 0;// 锁对象ticketCondition = [[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 detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];// Override point for customization after application launch[window makeKeyAndVisible]} - (void)run{while (TRUE) {// 上锁[ticketsCondition lock];if(tickets > 0){[NSThread sleepForTimeInterval:0.5];count = 100 - tickets;NSLog(@"当前票数是:%d,售出:%d,线程名:%@",tickets,count,[[NSThread currentThread] name]);tickets--;}else{break;}[ticketsCondition unlock];}} - (void)dealloc {[ticketsThreadone release];[ticketsThreadtwo release];[ticketsCondition release];[window release];[super dealloc];}@end

三、线程的交互
线程在运行过程中,可能需要与其它线程进行通信,如在主线程中修改界面等等,可以使用如下接口:

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

由于在本过程中,可能需要释放一些资源,则需要使用NSAutoreleasePool来进行管理,如:

- (void)startTheBackgroundJob {NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];// to do something in your thread job...[self performSelectorOnMainThread:@selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];[pool release];}

iOS4 已经支持多线程了,我的EASYWEB在打开多个网页时会卡得要命,决定把它改成多线程方式进行加载网页
iOS4的多线程,基于Objective-c 相对 C++ JAVA来说简单不少


技术要点:
一 线程创建与启动
线程类 NSThread

包含如下线程操作方法:

 //返回当前线程+ (NSThread *)currentThread;           // 通过类方法创建一个线程+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; // 判断是否为多线程+ (BOOL)isMultiThreaded; - (NSMutableDictionary *)threadDictionary; + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; //  退出线程+ (void)exit// 线程属性值+ (double)threadPriority ;+ (BOOL)setThreadPriority:(double)p ; // 线程函数地址+ (NSArray *)callStackReturnAddresses; // 设置与返回线程名称- (void)setName:(NSString *)n;- (NSString *)name; // 线程堆栈- (NSUInteger)stackSize;- (void)setStackSize:(NSUInteger)s; // 判断当前线程是否为主线程- (BOOL)isMainThread;+ (BOOL)isMainThread; + (NSThread *)mainThread; // 线程对象初始化操作   (通过创建线程对象 ,需要 手工指定线程函数与各种属性)- (id)init; // 在线程对象初始化时创建一个线程(指定线程函数)- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument; // 是否在执行- (BOOL)isExecuting; // 是否已经结束 - (BOOL)isFinished; // 是否取消的- (BOOL)isCancelled; // 取消操作- (void)cancel; // 线程启动- (void)start; - (void)main;    // thread body method

推荐方式

// 通过类方法创建一个线程+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument; // 在线程对象初始化时创建一个线程(指定线程函数)- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;

主要通过selector:(SEL)selector 指定个功能函数,系统使其与主线程分开运行,以达到多线程的效果.

以上方式创建线程,非类方法创建需要调用 start才能让线程真正运行起来.

当多个线程同时运行,就会出现访问资源的同步问题

二 线程同步操作

IPHONE 使用NSCondition来进行线程同步,它是IPHONE的锁对象,用来保护当前访问的资源.

大致使用方法

NSCondition* mYLock = [[NSCondition alloc] init][mYLock lock]

资源….

[mYLock unLock][mYLock release];

三 线程的交互

使用线程对象的

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

进行交互操作
主要是调用 主线程中指定的方法来执行一些相关操作

四 线程池 NSOperation
NSInvocationOperation是 NSOperation的子类 具体使用代码

// 建立一个操作对象 NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod:) object:data]// 将操作对象 加到系统已经的操作对列里, 这时候 myTaskMethod以一个线程的方式与主线程分开执行.[[MyAppDelegate sharedOperationQueue] addOperation:theOp]// 这个是真正运行在另外一个线程的“方法”- (void)myTaskMethod:(id)data{    // Perform the task.}

以上是使用系统操作对列,可以使用 NSOperationQueue创建自己的线程对列

NSOperationQueue *operationQueue;         operationQueue = [[NSOperationQueue alloc] init]; //初始化操作队列        [operationQueue setMaxConcurrentOperationCount:n]; // 可以设置队列的个数        [operationQueue addOperation:otherOper];

线程创建与撤销遵循 OC的内存管理规则.


线程间通信,交互
1,在应用程序主线程中做事情:
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
2,在指定线程中做事情:

performSelector:onThread:withObject:waitUntilDone:

performSelector:onThread:withObject:waitUntilDone:modes:
3,在当前线程中做事情:
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:
4,取消发送给当前线程的某个消息
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:


使用NSOperation和NSOperationQueue启动多线程开发应用

Apple从os x10.5在多线程应用的开发上有了很多改进,NSThread的引入使得开发多线程应用程序变得容易多了,尤其是引入了两个全新的类:NSOperation和NSOperationQueue.NSOperation对象类似java.lang.Runnable接口,也被设计为可扩展的,而且只有一个需要重写的方法, 这就是-(void)main.

The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task. Because it is abstract, you do not use this class directly but instead subclass or use one of the system-defined subclasses (NSInvocationOperation or NSBlockOperation

       使用NSOperation的最简单的方式就是把一个NSOperation对象加入到NSOperationQueue队列中,一旦这个对象被加入到队列,队列就开始处理这个对象,直到这个对象的所有操作完成,然后它被队列释放下面示例:使用一个获取网页,并对其解析的线程NSXMLDocument,最后将解析得到的NSXMLDocument再返回给主线程。

复制代码
// PageLoadOperation.h

@interface PageLoadOperation : NSOperation
{
NSURL *targetURL;
}
@property (retain) NSURL *targetURL;
- (id) initWithURL:(NSURL *) url;
@end

// PageLoadOperation.m
#import "PageLoadOperation.h"
#import "AppDelegate.h"

@implementation PageLoadOperation

@synthesize targetURL;

- (
id) initWithURL:(NSURL *)url
{
if(![super init]) return nil;
[self setTargetURL:url];
return self;
}

- (
void)dealloc
{
[targetURL release];
[super dealloc];
}

- (
void)main
{
NSString *webpageString = [[[NSString alloc] initWithContentsOfURL:[self targetURL]] autorelease];
NSError *error = nil;
NSXMLDocument *document = [[NSXMLDocument alloc] initWithXMLString:webpageString options:NSXMLDocuemtTidyHTML error:&error];
if (!document){
NSLog(
@"%s Error loading document(%@):%@",_cmd,[[self targetURL] absoluteString], error];
return;
}
[[AppDelegate shared] performSelectorOnMainThread:@selector(pageLoaded:) withObject:document waitUntilDon:YES];
[document release];
}
@end
复制代码

正是这样,该类很简单,只是在其init方法中接受一个url并保存起来,当main函数被调用的时候,它使用这个保存的url创建一个字符串,并将其传递给NSXMLDocumentinit方法。如果加载的xml数据没有出错,数据会被传递给AppDelegate,它处于主线程中。到此,这个线程的任务就宣告完成了。在主线程中注销操作队列时,会将这个NSOperation对象释放。

复制代码
// AppDelegate.h
@interface AppDelegate : NSObject {
NSOperationQueue *queue;
}

+ (
id)shared;
- (
void)pageLoaded:(NSXMLDocument *)document;
@end

//AppDelegate.m
#import "AppDelegate.h"
#import "PageLoadOperation.h"
@implementation AppDelegate
static AppDelegate *shared;
static NSArray *urlArray;
- (
id)init
{
if(shared)
{
[self autorelease];
return shared;
}
if(![super init]) return nil;
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:
@"http://www.google.com"];
[array addObject:
@"http://www.apple.com"];
[array addObject:
@"http://www.yahoo.com"];
[array addObject:
@"http://www.zarrastudios.com"];
[array addObject:
@"http://www.macosxhints.com"];
urlArray = array;
queue = [[NSOperationQueue alloc] init];
shared = self;
return self;
}
- (
void)applicationDidFinishLaunching:(NSNotification *)aNatification
{
for(NSString *urlString in urlArray)
{
NSURL *url = [NSURL URLWithString:urlString];
PageLoadOperation *plo = [[PageLoadOperation alloc] initWithURL:url];
[queue addOperation:plo];

[plo release];
}
}

- (
void)dealloc
{
[queue release];
[super dealloc];
}

+ (
id)shared
{
if(!shared){
[[AppDelegate alloc] init];
}
return shared;
}

- (
void)pageLoaded:(NSXMLDocument *)document
{
NSLog(
@"%s Do something with the XMLDocuemt:%@",_cmd,document);
}
@end
复制代码

实践问题:在iOS4.0后,如果是并发操作则NSURLConnection无法在NSOperation中正常运行了。即NSURLConnection只能运行于主线程。解决方法可以借鉴以下代码:

复制代码
-(void)download
{
// NSURLConnectionn won't work if it's not in the main thread
if (![NSThread isMainThread])
{
[self performSelectorOnMainThread:@selector(download) withObject:nil waitUntilDone:NO];
return;
}

NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:_urlString]];
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
复制代码

这个问题,其实确切是在iOS3中,并发的Operation都是在主线程中执行的,而到了iOS4.0,并发的Operation会在一个独立的线程中执行,这样就产生问题,如果不做特殊的处理,当Operation有异步的调用时,比如NSURLConnection,会由于Operation的start执行之后线程退出了而导致收不到相关的delegate调用。这个问题的标准解决之道是:在NSOperation发出了异步请求之后,启动线程内的runloop。确保线程不退出,同时收到delegate调用。