本文对iOS系统上的多线程进行了深入的讲解。
多任务的线程处理方式
对于不同的task任务,处理的方式和策略可以参考以下表格。
Technology | Description |
---|---|
Operation objects | 用队列管理一组task任务,内部使用一或者多个线程 |
GCD(Grand Central Dispatch) | 用队列管理一组task任务,内部使用一个线程 |
Idle-time notifications | 对于短且低优先级的task,可以通过向NSNotificationQueue发送一个包含NSPostWhenIdle选项的通知,当RunLoop处于Idle状态时,系统会处理该task |
Asynchronous functions | 一些带有异步功能的方法 |
Timer | 在一个线程中利用RunLoop的TimerSources进行执行,一般在主线程中使用 |
Separate processes | 用多进程来处理事务 |
线程库
iOS和OS X系统提供了以下的线程库:
Technology | Description |
---|---|
Cocoa threads | OC编写的库,包含NSThread类 |
POSIX threads | C编写的库,包含POSIX Threads |
Multiprocessing Services | 遗留的库,尽量避免使用 |
线程的成本
空间成本
每个线程需要占用内核和应用两边的内存空间。当线程被创建时,这两部分的内存空间即被占用。
Item | Approximate cost | Notes |
---|---|---|
Kernel data structures | 约1KB | 用于存储线程本身的结构和属性,不能被分页到硬盘 |
Stack space | 1MB (iOS主线程) 8MB(OSX 主线程)512KB(创建的线程) | 最小的创建线程的栈空间大小是16KB,且必须是4KB的倍数,内存空间在创建时即被分配,但是只有需要用时,真实的内存分页才会被创建) |
Creation time | 约90ms | 从开始创建线程到线程入口开始执行的时间 |
时间成本
而线程创建的时间,取决于处理器的负载、计算机的速度以及程序的内存等,只能进行粗略的估算,参考上表。
产品成本
多线程可能增加产品的不稳定性、开发的复杂性,应谨慎考虑。
线程创建
NSThread
NSThread有两种创建方式。
1 | //第一种 |
POSIX Threads
1 | #include <assert.h> |
通过NSObject派生线程
NSObject可以直接创建一个线程,并将对应的方法作为线程的执行入口。
1 | [myObj performSelectorInBackground:@selector(doSomething) withObject:nil]; |
线程属性配置
设置线程的栈空间
Technology | Description |
---|---|
Cocoa | 在start前用setStackSize:方法设置 |
POSIX | 创建pthread_attr_t结构体和使用pthread_attr_setstacksize方法设置栈空间大小,在创建时传递给pthread_create方法 |
Multiprocessing Services | MPCreateTask参数设置 |
设置线程属性
1 | NSThread:threadDictionary |
设置线程独立状态
一般情况下,线程都是detached(独立)的,当task完成时可立即被释放。必要时,也可以使用joinable线程,joinable意味着该线程只有被另一个线程join的时候,才会被释放。
1 | joinable线程可以通过pthread_exit传递数据给调用pthread_join的线程。 |
设置线程的优先级
可以通过以下函数来设置线程优先级。
1 | NSThread:setThreadPriority |
注意:即使设置了线程的优先级,也不能保证该线程一定会先被执行,只是有更大的可能性。
编写线程入口方法
使用ARC
ARC可以自动对线程的资源进行释放。
异常处理
线程的异常如果没有通过try/catch进行捕获,将会导致程序crash。
建立一个RunLoop
RunLoop可以帮你处理多任务,参考iOS-RunLoop详解;
终止线程
尽管每种线程库都提供了终止线程的方法,但是最好让线程自己执行到结束,自然终止,有利于线程自己管理空间释放。
NSThread可以使用RunLoop来检测自身是否可以终止。
1 | - (void)threadMainRoutine |