Threading Programming Guide 2 Thread Management
iOS线程编程手册-2-线程管理
每一个进程(应用程序)都是由一个或者多个线程组成,每一个都有自己的执行路径(path of execution)。每一个应用程序的启动都是从主线程开始,然后再创建和启动其他线程。
当一个进程成功地创建/启动了更加多的线程,这些线程们在同一个进程里面独立的运行。他们有自己的execution stack并且由kernel指定计划在runtime中启动运行。一个线程可以和其他线程通信、可以实施I/O操作和其他任何你想干的事情。又因为他们是在同一个进程里面运行的,所以他们和主线程共享虚拟内存(virtual memory space)和访问权限。
Thread Costs 线程开销
一个线程的创建需要在内核和应用程序内存分配空间。kernel用来管理线程的代码结构放在操作系统的联动内存(wired memory)里面,而线程任务代码需要操作的数据则保存在应用程序内存中。
下表估计和用户应用程序层面创建内存所需要的开销。其中一些是可以配置的,例如stack space。线程的创建时间容易受当前进程/系统负载的剧烈波动。
Item 项目 | Approximate cost 估计开销 | Notes 备注 |
---|---|---|
Kernel data strucures | Approximately 1 KB | 这个占用是用来保存线程数据结构和相关属性的,其中的大部分将保存在系统的联动内存里面,所以不能被写入到硬盘。 |
Stack space | 512KB(第二个线程) 8MB(OS X主线程)1MB(iOS主线程) | 第二个线程Stack space最小值是16KB(并且必须是4KB的倍数)。这块内存空间会放在应用程序虚拟内存空间,但是只有在他们需要用到的时候才会真正创建。 |
Creatation time 创建时间 | Approximately 90 microseconds | 这是时长是从调用线程init开始到线程任务准备好开始执行的间隔。测试平台:an Intel-based iMac with a 2 GHz Core Duo processor and 1 GB of RAM running OS X v10.5. |
因为Operation objects有kernel支持,往往创建速度会更加快。kernel使用线程池来管理线程,同时节省线程创建时间。
Creating a Thread 创建线程
启动一个low-level线程需要一个作为线程执行入口的函数/方法,然后在一个已有线程里面来启动它。
Using NSThread
- 使用
detachNewThreadSelector:toTarget:withObject:
类方法 - 创建一个新的
NSThread
object然后start
两者创建的线程都是detached的,在运行完之后会被系统自动回收资源。
[NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];
NSThread* myThread = [[NSThread alloc] initWithTarget:self
selector:@selector(myThreadMainMethod:)
object:nil];
[myThread start]; // Actually create the thread
使用performSelector:onThread:withObject:waitUntilDone:
方法可以发送消息给已经存在并且正在运行的thread。这个方法可以很方便地实现线程通信,虽然你还是有可能需要使用一些同步访问方法。receiver thread必须有run loop。不适合用在实时性要求高和线程平凡通信的场合。
Using POSIX Threads
OS X和iOS均提供基于C的POSIX 线程 API。
POSIX线程默认是joinable的,需要设置成detached。这样才可以让系统在线程结束后马上回收线程资源。
#include <assert.h>
#include <pthread.h>
void* PosixThreadMainRoutine(void* data)
{
// Do some work here.
return NULL;
}
void LaunchThread()
{
// Create the thread using POSIX routines.
pthread_attr_t attr;
pthread_t posixThreadID;
int returnVal;
returnVal = pthread_attr_init(&attr);
assert(!returnVal);
returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
assert(!returnVal);
int threadError = pthread_create(&posixThreadID, &attr,
&PosixThreadMainRoutine, NULL);
returnVal = pthread_attr_destroy(&attr);
assert(!returnVal);
if (threadError != 0)
{
// Report an error.
}
}
POSIX线程通信可以使用ports、conditions或者shred memory来实现。对于需要长时间运行(long-lived)的线程,你应该设立一些内部通信机制来判断进程主线程状态和在主线程被关闭时做好clean up。
Using NSObject to spawn a Thread 使用NSObject衍生线程
[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
detached.
这和你创建一个NSThread然后调用detachNewThreadSelector:toTarget:withObject:
是一样的。
线程会马上创建然后尽快运行。
创建之前要设置好object(上面例子的myObj
),例如创建一个autorelease pool。
详见后面的 Configuring Thread Attributes 。
Using POSIX Threads in a Cocoa Application 在Cocoa程序中使用POSIX
虽然NSThread是Cocoa中使用线程的主要接口,但是你可以方便地使用POSIX Thread API。
Protecting the Cocoa Frameworks 使用POSIX thread时保护好Cocoa框架
为了防止同步锁机制降低单线程应用程序的性能,Cocoa会在你使用NSThread的时候才让自己进入Multithread环境创建同步锁先关数据结构。不能在单线程模式下使用POSIX线程和同步锁。如果不能确定当前是不是Multithread模式,你可以创建一个NSThread然后马上释放,Cocoa就会自动进入多线程模式了。
Mixing POSIX and Cocoa Locks 混合使用POSIX和Cocoa Locks
因为Cocoa Lock和Condition都是POSIX mutexes和Conditions的封装类型,所以可以和POSIX thread一起使用。但是你不能混合使用,例如不能使用NSLock来管理使用pthread_mutex_init
创建的mutex。
Configuring Thread Attributes 设置线程属性
Configuring the Stack Size of a Thread 设置线程Stack大小
对于不用框架下线程stack大小的设置方法:
Technology 技术/框架 | Option 选项/方法 |
---|---|
Cocoa | 创建了NSThread之后,在调用start 之前使用setStackSize: 方法。 |
POSIX | 创建一个pthread_attr_t 结构体,使用pthread_attr_setstacksize 函数设置。然后把结构体传入pthread_create 方法。 |
Multiprocessing Service | 直接把stack size传入MPCreateTask 方法。 |
Configuring Thread-Local Storage 设置线程本地存储
每一个线程都有一个key-value Dictionary可以用来保存整个线程生命周期的数据。
Cocoa和POSIX存储thread dictionary的方法不用,不要混淆。
Cocoa:使用NSThread类的threadDictionary
方法取得。
POSIX:使用pthread_setspecific
和pthread_getspecific
来访问。
Setting the Detached State of a Thread 把线程设置为Detached状态
Cocoa创建的NSThread默认是Detached的,运行完之后系统自动回收资源,不管你是否传回结果/状态给其他线程。
你可以把joinable thread当做子线程,虽然它可以独立运行,但是必须有其他线程join了之后才能终止。
Joinable thread则需要等待其他线程join之后才会被回收资源,退出之前可以把指针通过pthread_exit
方法传递出去,另一个thread可以调用pthread_join
方法获取该指针。
重要: 在应用程序退出的时候,Detached thread可以马上被系统终止,但是joinable thread不可以。每一个joinable thread都只能在被join了之后才允许退出。joinable thread用在不能被中断的任务上。
如果你需要创建joinable thread,使用POSIX thread创建。POSIX默认创建的线程就是joinable的。使用pthread_attr_setdetachstate
方法可以修改。线程开始运行后,你可以使用pthread_detach
方法改变为detach。 详情请 man pthread
/ man pthread_join
。
Setting the Thread Priority 设置线程优先级别
所有线程创建的时候都有一个默认的运行优先级别,kernel会把线程的优先级别当做其中一个因素来指定运行计划。
然而高优先级别也不能保证线程会有最多的运行时间。
如果你的线程们需要相互通信,最好不要设置它们的优先级别。不同优先级别的线程间的锁机制很有可能会降低性能。
Cocoa线程设置线程有线级别:使用NSThread的setThreadPriority
。详见 NSThread Class Reference 。
POSIX线程设置线程优先级别:使用pthread_setschedparam
。详见 man pthread_setschedparam
。
Writing Your Thread Entry Routine 一般线程要做的工作
一般来说,编写线程需要做以下步骤:
- 初始化数据
- 一些准备工作
- [可选]设置run loop
- 线程终止时Clean up
Creating an Autorelease Pool 创建autorelease pool
- (void)myThreadMainRoutine
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
// Do thread work here.
[pool release]; // Release the objects in the pool.
}
Setting Up an Exception Handler
You can use either the C++ or Objective-C exception handling style when building your project in Xcode. For information about setting how to raise and catch exceptions in Objective-C, see Exception Programming Topics .
Setting Up a Run Loop
见后面章节的 Run Loops 。
Terminating a Thread
对于需要在运行过程中终止的线程,需要给它创建一个run loop来接收线程终止消息。
- (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 isn't
waiting to fire.
[runLoop runUntilDate:[NSDate date]];
// Check to see if an input source handler changed the exitNow value.
exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];
}
}