1.线程简介
当我们需要处理一个非常繁重的任务的时候,为了避免阻塞主线程的执行(主线程主要负责用户交互和相关事件处理),我们需要使用线程。当我们使用线程来把很大的任务划分成一些小的任务在多核机器上并发的执行的时候,可以大大提高我们程序的性能。NSTread提供给我们了执行线程的管理。
2.线程开销
线程是需要内存和性能开销的,内存开销包括系统内核内存和应用程序内存。用来管理和协调线程的内核结构存储在内核。线程的栈空间和每个线程的数据存储在程序的内存空间。占用内存的这些结构大部分是在线程创建的时候生成和初始化的。因为要和内核交互,所以这个过程是非常耗时的。线程创建大概的开销如下(其中第二线程的栈空间是可以配置的
):
- 内核数据结构:大约1KB
- 占空间:主线程大约1MB,第二线程大约512KB
- 线程创建时间:大约90毫秒
另外一个开销就是程序内线程同步的开销。
3.创建线程
使用NSThread创建线程一共有4中方法:
- 使用NSThread类方法+detachNewThreadSelector:toTarget:withObject:创建线程并执行
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];
- 使用NSObject的线程扩展(NSThreadPerformAdditions)方法+performSelectorInBackground:withObject:创建并执行线程
[object performSelectorInBackground:@selector(myThreadMainMethod:) withObject:nil];
- 创建一个NSThread对象,然后调用start方法执行
NSThread* aThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil]; [aThread start];
- 创建一个NSThread子类,然后实例化调用start方法
#import @interface Thread : NSThread @end #import "Thread.h"@implementation Thread - (void)main { @autoreleasepool { //do thread task } } @end//调用 Thread *thread = [[Thread alloc] init]; [thread start];
4.配置线程参数
4.1配置线程栈空间
栈空间是用来存储为线程创建的本地变量的,栈空间的大小必须在线程的创建之前设定。不能使用创建线程的第一和第二种方法。在调用NSThread的
start
方法之前通过setStackSize:
设定新的栈空间大小。4.2配置线程的本地存储
每个线程都维护一个在线程任何地方都能获取的字典。 我们可以使用NSThread的
threadDictionary
方法获取一个NSMutableDictionary
对象,然后添加我们需要的字段和数据。4.3设置线程的Detached状态
通过NSThread创建的线程都是Detached的。如果想要创建joinable线程,只能通过POSIX线程接口创建。
4.4设置线程的优先级
每一个新的线程都有一个默认的优先级。系统的内核调度算法根据线程的优先级来决定线程的执行顺序。通常情况下我们不要改变线程的优先级,提高一些线程的优先级可能会导致低优先级的线程一直得不到执行,如果在我们的应用内存在高优先级线程和低优先级线程的交互的话,因为低优先级的线程得不到执行可能阻塞其他线程的执行。这样会对应用造成性能瓶颈。可以通过NSThread的
setThreadPriority:
方法设置线程优先级,优先级为0.0到1.0的double类型,1.0为最高优先级。5.完成线程入口
5.1创建一个Autorelease Pool
在线程的入口处我们需要创建一个Autorelease Pool,当线程退出的时候释放这个Autorelease Pool。这样在线程中创建的autorelease对象就可以在线程结束的时候释放,避免过多的延迟释放造成程序占用过多的内存。如果是一个长寿命的线程的话,应该创建更多的Autorelease Pool来达到这个目的。例如线程中用到了run loop的时候,每一次的迭代都需要创建Autorelease Pool。在非ARC的情况下创建Autorelease Pool代码如下:
- (void)myThreadMainRoutine { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool // Do thread work here. [pool release]; // Release the objects in the pool. }
在ARC环境的代码如下:
- (void)myThreadMainRoutine { @autoreleasepool { //do thread task } }
5.2设置异常处理
如果我们的应用能够捕获并处理异常,那我们自己创建的线程就应该捕获异常,如果不这样的话,当线程出问题的时候,应用就会Crash。
5.3设置Run Loop
当创建线程的时候我们有两种选择,一种是线程执行一个很长的任务然后再任务结束的时候退出。另外一种是线程可以进入一个循环,然后处理动态到达的任务。每一个线程默认都有一个NSRunloop,主线程是默认开启的,其他线程要手动开启。关于NSRunloop的理解和使用下一章讨论。
6.终止线程
终止线程最好不要用POSIX接口直接杀死线程,这种粗暴的方法会导致系统无法回收线程使用的资源,造成内存泄露,还有可能对程序的运行造成影响。终止线程最好的方式是能够让线程接收取消和退出消息,这样线程在受到消息的时候就有机会清理已持有的资源,避免内存泄露。这种方案的一种实现方式是使用NSRunloop的input source来接收消息,每一次的NSRunloop循环都检查退出条件是否为YES,如果为YES退出循环回收资源,如果为NO,则进入下一次NSRunloop循环。例子代码如下:
- (void)threadMainRoutine { BOOL moreWorkToDo = YES; BOOL exitNow = NO; NSRunLoop* runLoop = [NSRunLoop currentRunLoop]; // Add the exitNow BOOL to the thread dictionary. NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary]; [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"]; // Install an input source. [self myInstallCustomInputSource]; while (moreWorkToDo && !exitNow) { // Do one chunk of a larger body of work here. // Change the value of the moreWorkToDo Boolean when done. // Run the run loop but timeout immediately if the input source is not waiting to fire. [runLoop runUntilDate:[NSDate date]]; // Check to see if an input source handler changed the exitNow value. exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue]; } }
- 创建一个NSThread子类,然后实例化调用start方法
- 创建一个NSThread对象,然后调用start方法执行
- 使用NSObject的线程扩展(NSThreadPerformAdditions)方法+performSelectorInBackground:withObject:创建并执行线程
- 使用NSThread类方法+detachNewThreadSelector:toTarget:withObject:创建线程并执行