- 快召唤伙伴们来围观吧
- 微博 QQ QQ空间 贴吧
- 文档嵌入链接
- 复制
- 微信扫一扫分享
- 已成功复制到剪贴板
Mac OS X 多线程编程指南
展开查看详情
1 .多线程编程指南 原著:Apple Inc. 翻译:謝業蘭 【老狼】 联系:xyl.layne@gmail.com 鸣谢:有米移动广告平台 CocoaChina 社区
2 . Threading Programming Guide 目录 多线程编程指南 ................................................................................................................................................ I 简介 ................................................................................................................................................................... 1 本文档结构............................................................................................................................ 1 第一章 关于多线程编程 .......................................................................................................................... 2 1.1 什么是多线程............................................................................................................ 2 1.2 线程术语.................................................................................................................... 3 1.3 多线程的替代方法 .................................................................................................... 3 1.4 线程支持.................................................................................................................... 5 1.4.1 线程包 ............................................................................................................. 5 1.4.2 Run Loops ....................................................................................................... 6 1.4.3 同步工具 ......................................................................................................... 6 1.4.4 线程间通信 ..................................................................................................... 7 1.5 设计技巧.................................................................................................................... 8 1.5.1 避免显式创建线程 ......................................................................................... 8 1.5.2 保持你的线程合理的忙 ................................................................................. 9 1.5.3 避免共享数据结构 ......................................................................................... 9 1.5.4 多线程和你的用户界面 ................................................................................. 9 1.5.5 了解线程退出时的行为 ............................................................................... 10 1.5.6 处理异常 ........................................................................................................11 1.5.7 干净地中断你的线程 ....................................................................................11 1.5.8 线程安全的库 ................................................................................................11 第二章 线程管理 .................................................................................................................................... 13 2.1 线程成本.................................................................................................................. 13 2.2 创建一个线程.......................................................................................................... 14 2.2.1 使用 NSThread.............................................................................................. 14 2.2.2 使用 POSIX 的多线程 ................................................................................. 16 2.2.3 使用 NSObject 来生成一个线程 ................................................................. 18 2.2.4 使用其他线程技术 ....................................................................................... 18 2.2.5 在 Cocoa 程序上面使用 POSIX 线程 ......................................................... 19 2.3 配置线程属性.......................................................................................................... 19 2.3.1 配置线程的堆栈大小 ................................................................................... 20 2.3.2 配置线程本地存储 ....................................................................................... 20 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [1]
3 . Threading Programming Guide 2.3.3 设置线程的脱离状态 ................................................................................... 21 2.3.4 设置线程的优先级 ....................................................................................... 21 2.4 编写你线程的主体入口点 ...................................................................................... 22 2.4.1 创建一个自动释放池(Autorelease Pool) ................................................ 22 2.4.2 设置异常处理 ............................................................................................... 23 2.4.3 设置一个 Run Loop ...................................................................................... 23 2.5 中断线程.................................................................................................................. 24 第三章 RUN LOOPS ............................................................................................................................. 26 3.1 RUN LOOP 剖析 ........................................................................................................ 26 3.1.1 Run Loop 模式 ............................................................................................. 27 3.1.2 输入源 ........................................................................................................... 28 3.2 何时使用 RUN LOOP................................................................................................ 33 3.3 使用 RUN LOOP 对象 ............................................................................................... 34 3.3.1 获得 Run Loop 对象 ..................................................................................... 34 3.3.2 配置 Run Loop .............................................................................................. 34 3.3.3 启动 Run Loop .............................................................................................. 36 3.3.4 退出 Run Loop .............................................................................................. 38 3.3.5 线程安全和 Run Loop 对象 ......................................................................... 38 3.4 配置 RUN LOOP 的源............................................................................................... 39 3.4.1 定义自定义输入源 ....................................................................................... 39 3.4.2 配置定时源 ................................................................................................... 45 3.4.3 配置基于端口的输入源 ............................................................................... 46 第四章 线程同步 .................................................................................................................................... 57 4.1 同步工具.................................................................................................................. 57 4.1.1 原子操作 ....................................................................................................... 57 4.1.2 内存屏障和 Volatile 变量 ........................................................................... 58 4.1.3 锁 ................................................................................................................... 58 4.1.4 条件 ............................................................................................................... 59 4.1.5 执行 Selector 例程 ........................................................................................ 60 4.2 同步的成本和性能 .................................................................................................. 60 4.3 线程安全和信号量 .................................................................................................. 61 4.4 线程安全设计的技巧 .............................................................................................. 62 4.4.1 完全避免同步 ............................................................................................... 62 4.4.2 了解同步的限制 ........................................................................................... 62 4.4.3 注意对代码正确性的威胁 ........................................................................... 62 4.4.4 当心死锁(Deadlocks)和活锁(Livelocks) ................................................ 64 4.4.5 正确使用 Volatile 变量 ................................................................................. 65 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [2]
4 . Threading Programming Guide 4.5 使用原子操作.......................................................................................................... 65 4.6 使用锁...................................................................................................................... 67 4.6.1 使用 POSIX 互斥锁 ..................................................................................... 68 4.6.2 使用 NSLock 类............................................................................................ 68 4.6.3 使用@synchronized 指令 ............................................................................. 69 4.6.4 使用其他 Cocoa 锁 ....................................................................................... 70 4.7 使用条件.................................................................................................................. 73 4.7.1 使用 NSCondition 类 .................................................................................... 73 4.7.2 使用 POSIX 条件 ......................................................................................... 74 附录 A:线程安全总结 ................................................................................................................................. 76 COCOA ................................................................................................................................. 76 基础框架(Fondation Framework)的线程安全 ........................................................ 76 Application Kit 框架的线程安全 .................................................................................. 82 Core Data 框架 .............................................................................................................. 84 CORE FOUNDATION(核心框架) ...................................................................................... 84 术语表 ............................................................................................................................................................. 86 结束语 ............................................................................................................................................................. 88 推荐资源 ......................................................................................................................................................... 89 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [3]
5 . Threading Programming Guide 简介 线程是单个应用中可以并发执行多个代码路径的多种技术之一。虽然更新的技术 如操作对象(Operation objects)和 Grand Central Dispatch(GCD),提供一个更 加现代化和高效率的基础设施来实现多核并发,但是 Mac OS X 和 iOS 也提供一套接 口来创建和管理线程。 本文档介绍了 Mac OS X 上面的可用线程包,并且演示如何使用它们。本文档还 描述了在你的应用中多线程代码同步的相关技术。 重要:如果你正在创建一个新的应用程序,我们鼓励你研究 Mac OS X 上面实现并发的替代 方法。如果还没有熟悉掌握需要实现一个多线程应用的设计技术的话,我们更鼓励你那样做。这 些替代方法简化了大量原本你需要实现来执行并发路径的工作,并且提供了比传统线程更好的性 能。获取更多相关技术的信息,你可以查阅Concurrency Programming Guide。 本文档结构 本篇文档包含了以下章节和附录: “关于多线程编程”介绍了多线程的概念和它们在应用设计里面的角色。 “线程管理”提供了关于 Mac OS X 上面线程技术的相关信息,并且教你如果 使用它们。 “Run Loops” 提供有关如何管理在辅助线程中的循环事件处理的信息。 “同步(Synchronization)” 介绍同步问题和你可以用于阻止多线程破坏你 的数据或者导致你程序崩溃的工具。 “线程安全总结” 提供了 Mac OS X 和 iOS 上面固有的线程安全的高度总结 和它们的主要框架 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [1]
6 . Threading Programming Guide 第一章 关于多线程编程 多年来,计算机的最大性能主要受限于它的中心微处理器的速度。然而由于个别 处理器已经开始达到它的瓶颈限制,芯片制造商开始转向多核设计,让计算机具有了 同时执行多个任务的能力。尽管 Mac OS X 利用了这些核心优势,在任何时候可以执 行系统相关的任务,但自己的应用程序也可以通过多线程方法利用这些优势。 1.1 什么是多线程 多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径。在系 统级别内,程序并排执行,系统分配到每个程序的执行时间是基于该程序的所需时间 和其他程序的所需时间来决定的。然而在每个应程序的内部,存在一个或多个执行线 程,它同时或在一个几乎同时发生的方式里执行不同的任务。系统本身管理这些执行 的线程,调度它们在可用的内核上运行,并在需要让其他线程执行的时候抢先打断它 们。 从技术角度来看,一个线程就是一个需要管理执行代码的内核级和应用级数据结 构组合。内核级结构协助调度线程事件,并抢占式调度一个线程到可用的内核之上。 应用级结构包括用于存储函数调用的调用堆栈和应用程序需要管理和操作线程属性 和状态的结构。 在非并发的应用程序,只有一个执行线程。该线程开始和结束于你应用程序的 main 循环,一个个方法和函数的分支构成了你整个应用程序的所有行为。与此相反, 支持并发的应用程序开始可以在需要额外的执行路径时候创建一个或多个线程。每个 新的执行路径有它自己独立于应用程序 main 循环的定制开始循环。在应用程序中存 在多个线程提供了两个非常重要的的潜在优势: 多个线程可以提高应用程序的感知响应。 多个线程可以提高应用程序在多核系统上的实时性能。 如果你的应用程序只有单独的线程,那么该独立程序需要完成所有的事情。它必 须对事件作出响应,更新您的应用程序的窗口,并执行所有实现你应用程序行为需要 的计算。拥有单独线程的主要问题是在同一时间里面它只能执行一个任务。那么当你 的应用程序需要很长时间才能完成的时候会发生什么呢?当你的代码忙于计算你所 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [2]
7 . Threading Programming Guide 需要的值的时候,你的程序就会停止响应用户事件和更新它的窗口。如果这样的情况 持续足够长的时间,用户就会误认为你的程序被挂起了,并试图强制退出。如果你把 你的计算任务转移到一个独立的线程里面,那么你的应用程序主线程就可以自由并及 时响应用户的交互。 当然多线程并不是解决程序性能问题的灵丹妙药。多线程带来好处同时也伴随着 潜在问题。应用程序内拥有多个可执行路径,会给你的代码增加更多的复杂性。每个 线程需要和其他线程协调其行为,以防止它破坏应用程序的状态信息。因为应用程序 内的多个线程共享内存空间,它们访问相同的数据结构。如果两个线程试图同时处理 相同的数据结构,一个线程有可能覆盖另外线程的改动导致破坏该数据结构。即使有 适当的保护,你仍然要注意由于编译器的优化导致给你代码产生很微妙的(和不那么 微妙)的 Bug。 1.2 线程术语 在讨论多线程和它支持的相关技术之前,我们有必要先了解一些基本的术语。如 果你熟悉 Carbon 的多处理器服务 API 或者 UNIX 系统的话,你会发现本文档里面“任 务(task)”被用于不同的定义。在 Mac OS 的早期版本,术语“任务(task)”是用来 区分使用多处理器服务创建的线程和使用 Carbon 线程管理 API 创建的线程。在 UNIX 系统里面,术语“任务(task)”也在一段时间内被用于指代运行的进程。在实际应 用中,多处理器服务任务是相当于抢占式的线程。 由于 Carbon 线程管理器和多处理器服务 API 是 Mac OS X 的传统技术,本文件采 用下列术语: 线程(线程)用于指代独立执行的代码段。 进程(process)用于指代一个正在运行的可执行程序,它可以包含多个线程。 任务(task)用于指代抽象的概念,表示需要执行工作。 1.3 多线程的替代方法 你自己创建多线程代码的一个问题就是它会给你的代码带来不确定性。多线程是 一个相对较低的水平和复杂的方式来支持你的应用程序并发。如果你不完全理解你的 设计选择的影响,你可能很容易遇到同步或定时问题,其范围可以从细微的行为变化 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [3]
8 . Threading Programming Guide 到严重到让你的应用程序崩溃并破坏用户数据。 你需要考虑的另一个因素是你是否真的需要多线程或并发。多线程解决了如何在 同一个进程内并发的执行多路代码路径的问题。然而在很多情况下你是无法保证你所 在做的工作是并发的。多线程引入带来大量的开销,包括内存消耗和 CPU 占用。你会 发现这些开销对于你的工作而言实在太大,或者有其他方法会更容易实现。 表 1-1 列举了多线程的替代方法。该表包含了多线程的替代技术(比如操作对象 和 GCD)和如何更高效的使用单个线程。 Table 1-1 Alternative technologies to threads Technology Description Introduced in Mac OS X v10.5, an operation object is a wrapper for a task that would normally be executed on a secondary thread. This wrapper hides the thread management aspects of performing the task, leaving you free to focus on the task itself. You typically Operation use these objects in conjunction with an operation queue object, which actually manages objects the execution of the operation objects on one more threads. For more information on how to use operation objects, see Concurrency Programming Guide. Introduced in Mac OS x v10.6, Grand Central Dispatch is another alternative to threads that lets you focus on the tasks you need to perform rather than on thread management. With GCD, you define the task you want to perform and add it to a work queue, which Grand Central handles the scheduling of your task on an appropriate thread. Work queues take into Dispatch (GCD) account the number of available cores and the current load to execute your tasks more efficiently than you could do yourself using threads. For information on how to use GCD and work queues, see Concurrency Programming Guide For tasks that are relatively short and very low priority, idle time notifications let you perform the task at a time when your application is not as busy. Cocoa provides support Idle-time for idle-time notifications using the NSNotificationQueue object. To request an idle-time notifications notification, post a notification to the default NSNotificationQueue object using the NSPostWhenIdle option. The queue delays the delivery of your notification object until the run loop becomes idle. For more information, see Notification Programming Topics. The system interfaces include many asynchronous functions that provide automatic concurrency for you. These APIs may use system daemons and processes or create Asynchronous custom threads to perform their task and return the results to you. (The actual functions implementation is irrelevant because it is separated from your code.) As you design your application, look for functions that offer asynchronous behavior and consider using them instead of using the equivalent synchronous function on a custom thread. You can use timers on your application’s main thread to perform periodic tasks that are Timers too trivial to require a thread, but which still require servicing at regular intervals. For information on timers, see “Timer Sources.” Although more heavyweight than threads, creating a separate process might be useful in cases where the task is only tangentially related to your application. You might use a Separate process if a task requires a significant amount of memory or must be executed using root processes privileges. For example, you might use a 64-bit server process to compute a large data set while your 32-bit application displays the results to the user. 注意:当使用 fork 函数加载独立进程的时候,你必须总是在 fork 后面调用 exec 或者类似的函数。 基于 Core Foundation、Cocao 或者 Core Data 框架(无论显式还是隐式关联)的应用程序随后 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [4]
9 . Threading Programming Guide 调用 exec 函数或者类似的函数都会导出不确定的结果。 1.4 线程支持 如果你已经有代码使用了多线程,Mac OS X 和 iOS 提供几种技术来在你的应用程 序里面创建多线程。此外,两个系统都提供了管理和同步你需要在这些线程里面处理 的工作。以下几个部分描述了一些你在 Mac OS X 和 iOS 上面使用多线程的时候需要 注意的关键技术。 1.4.1 线程包 虽然多线程的底层实现机制是 Mach 的线程,你很少(即使有)使用 Mach 级的线 程。相反,你会经常使用到更多易用的 POSIX 的 API 或者它的衍生工具。Mach 的实 现没有提供多线程的基本特征,但是包括抢占式的执行模型和调度线程的能力,所以 它们是相互独立的。 列表 1-2 列举你可以在你的应用程序使用的线程技术。 Table 1-2 Thread technologies Technology Description Cocoa implements threads using the NSThread class. Cocoa also provides methods on Cocoa threads NSObject for spawning new threads and executing code on already-running threads. For more information, see “Using NSThread” and “Using NSObject to Spawn a Thread.” POSIX threads provide a C-based interface for creating threads. If you are not writing a Cocoa application, this is the best choice for creating threads. The POSIX interface is POSIX threads relatively simple to use and offers ample flexibility for configuring your threads. For more information, see “Using POSIX Threads” Multiprocessing Services is a legacy C-based interface used by applications transitioning from older versions of Mac OS. This technology is available in Mac OS X only and should Multiprocessing be avoided for any new development. Instead, you should use the NSThread class or Services POSIX threads. If you need more information on this technology, see Multiprocessing Services Programming Guide. 在应用层上,其他平台一样所有线程的行为本质上是相同的。线程启动之后,线 程就进入三个状态中的任何一个:运行(running)、就绪(ready)、阻塞(blocked)。如 果一个线程当前没有运行,那么它不是处于阻塞,就是等待外部输入,或者已经准备 就绪等待分配 CPU。线程持续在这三个状态之间切换,直到它最终退出或者进入中断 状态。 当你创建一个新的线程,你必须指定该线程的入口点函数(或 Cocoa 线程时候为 入口点方法)。该入口点函数由你想要在该线程上面执行的代码组成。但函数返回的 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [5]
10 . Threading Programming Guide 时候,或你显式的中断线程的时候,线程永久停止,且被系统回收。因为线程创建需 要的内存和时间消耗都比较大,因此建议你的入口点函数做相当数量的工作,或建立 一个运行循环允许进行经常性的工作。 为了获取更多关于线程支持的可用技术并且如何使用它们,请阅读“线程管理部 分”。 1.4.2 Run Loops 注:为了便于记忆,文本后面部分翻译 Run Loops 的时候基本采用原义,而非翻译为“运行 循环”。 一个 run loop 是用来在线程上管理事件异步到达的基础设施。一个 run loop 为 线程监测一个或多个事件源。当事件到达的时候,系统唤醒线程并调度事件到 run loop,然后分配给指定程序。如果没有事件出现和准备处理,run loop 把线程置于休 眠状态。 你创建线程的时候不需要使用一个 run loop,但是如果你这么做的话可以给用户 带来更好的体验。Run Loops 可以让你使用最小的资源来创建长时间运行线程。因为 run loop 在没有任何事件处理的时候会把它的线程置于休眠状态,它消除了消耗 CPU 周期轮询,并防止处理器本身进入休眠状态并节省电源。 为了配置 run loop,你所需要做的是启动你的线程,获取 run loop 的对象引用, 设置你的事件处理程序,并告诉 run loop 运行。Cocoa 和 Carbon 提供的基础设施会 自动为你的主线程配置相应的 run loop。如果你打算创建长时间运行的辅助线程, 那么你必须为你的线程配置相应的 run loop。 关于 run loops 的详细信息和如何使用它们的例子会在“Run Loops”部分介绍。 1.4.3 同步工具 线程编程的危害之一是在多个线程之间的资源争夺。如果多个线程在同一个时间 试图使用或者修改同一个资源,就会出现问题。缓解该问题的方法之一是消除共享资 源,并确保每个线程都有在它操作的资源上面的独特设置。因为保持完全独立的资源 是不可行的,所以你可能必须使用锁,条件,原子操作和其他技术来同步资源的访问。 锁提供了一次只有一个线程可以执行代码的有效保护形式。最普遍的一种锁是互 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [6]
11 . Threading Programming Guide 斥排他锁,也就是我们通常所说的“mutex”。当一个线程试图获取一个当前已经被其 他线程占据的互斥锁的时候,它就会被阻塞直到其他线程释放该互斥锁。系统的几个 框架提供了对互斥锁的支持,虽然它们都是基于相同的底层技术。此外 Cocoa 提供了 几个互斥锁的变种来支持不同的行为类型,比如递归。获取更多关于锁的种类的信息, 请阅读“锁”部分内容。 除了锁,系统还提供了条件,确保在你的应用程序任务执行的适当顺序。一个条 件作为一个看门人,阻塞给定的线程,直到它代表的条件变为真。当发生这种情况的 时候,条件释放该线程并允许它继续执行。POSIX 级别和基础框架都直接提供了条件 的支持。(如果你使用操作对象,你可以配置你的操作对象之间的依赖关系的顺序确 定任务的执行顺序,这和条件提供的行为非常相似)。 尽管锁和条件在并发设计中使用非常普遍,原子操作也是另外一种保护和同步访 问数据的方法。原子操作在以下情况的时候提供了替代锁的轻量级的方法,其中你可 以执行标量数据类型的数学或逻辑运算。原子操作使用特殊的硬件设施来保证变量的 改变在其他线程可以访问之前完成。 获取更多关于可用同步工具信息,请阅读“同步工具”部分。 1.4.4 线程间通信 虽然一个良好的设计最大限度地减少所需的通信量,但在某些时候,线程之间的 通信显得十分必要。(线程的任务是为你的应用程序工作,但如果从来没有使用过这 些工作的结果,那有什么好处呢?)线程可能需要处理新的工作要求,或向你应用程 序的主线程报告其进度情况。在这些情况下,你需要一个方式来从其他线程获取信息。 幸运的是,线程共享相同的进程空间,意味着你可以有大量的可选项来进行通信。 线程间通信有很多种方法,每种都有它的优点和缺点。“配置线程局部存储”列 出了很多你可以在 Mac OS X 上面使用的通信机制。(异常的消息队列和 Cocoa 分布式 对象,这些技术也可在 iOS 用来通信)。本表中的技术是按照复杂性的顺序列出。 Table 1-3 Communication mechanisms Mechanism Description Cocoa applications support the ability to perform selectors directly on other threads. This capability means that one thread can essentially execute a method on any other thread. Direct messaging Because they are executed in the context of the target thread, messages sent this way are automatically serialized on that thread. For information about input sources, see 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [7]
12 . Threading Programming Guide “Cocoa Perform Selector Sources.” Another simple way to communicate information between two threads is to use a global variable, shared object, or shared block of memory. Although shared variables are fast Global variables, and simple, they are also more fragile than direct messaging. Shared variables must be shared memory, carefully protected with locks or other synchronization mechanisms to ensure the and objects correctness of your code. Failure to do so could lead to race conditions, corrupted data, or crashes. Conditions are a synchronization tool that you can use to control when a thread executes a particular portion of code. You can think of conditions as gate keepers, letting a thread Conditions run only when the stated condition is met. For information on how to use conditions, see “Using Conditions.” A custom run loop source is one that you set up to receive application-specific messages on a thread. Because they are event driven, run loop sources put your thread to sleep Run loop sources automatically when there is nothing to do, which improves your thread’s efficiency. For information about run loops and run loop sources, see “Run Loops.” Port-based communication is a more elaborate way to communication between two threads, but it is also a very reliable technique. More importantly, ports and sockets can be used to communicate with external entities, such as other processes and services. Ports and sockets For efficiency, ports are implemented using run loop sources, so your thread sleeps when there is no data waiting on the port. For information about run loops and about port-based input sources, see “Run Loops.” The legacy Multiprocessing Services defines a first-in, first-out (FIFO) queue abstraction for managing incoming and outgoing data. Although message queues are simple and Message queues convenient, they are not as efficient as some other communications techniques. For more information about how to use message queues, see Multiprocessing Services Programming Guide. Distributed objects is a Cocoa technology that provides a high-level implementation of port-based communications. Although it is possible to use this technology for inter-thread Cocoa distributed communication, doing so is highly discouraged because of the amount of overhead it objects incurs. Distributed objects is much more suitable for communicating with other processes, where the overhead of going between processes is already high. For more information, seeDistributed Objects Programming Topics. 1.5 设计技巧 以下各节帮助你实现自己的线程提供了指导,以确保你代码的正确性。部分指南 同时提供如何利用你的线程代码获得更好的性能。任何性能的技巧,你应该在你更改 你代码之前、期间、之后总是收集相关的性能统计数据。 1.5.1 避免显式创建线程 手动编写线程创建代码是乏味的,而且容易出现错误,你应该尽可能避免这样做。 Mac OS X 和 iOS 通过其他 API 接口提供了隐式的并发支持。你可以考虑使用异步 API, GCD 方式,或操作对象来实现并发,而不是自己创建一个线程。这些技术背后为你做 了线程相关的工作,并保证是无误的。此外,比如 GCD 和操作对象技术被设计用来管 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [8]
13 . Threading Programming Guide 理线程,比通过自己的代码根据当前的负载调整活动线程的数量更高效。 关于更多 GCD 和操作对象的信息,你可以查阅“并发编程指南(Concurrency Programming Guid)”。 1.5.2 保持你的线程合理的忙 如果你准备人工创建和管理线程,记得多线程消耗系统宝贵的资源。你应该尽最 大努力确保任何你分配到线程的任务是运行相当长时间和富有成效的。同时你不应该 害怕中断那些消耗最大空闲时间的线程。线程使用一个平凡的内存量,它的一些有线, 所以释放一个空闲线程,不仅有助于降低您的应用程序的内存占用,它也释放出更多的物理 内存使用的其他系统进程。线程占用一定量的内存,其中一些是有线的,所以释放空闲线程 不但帮助你减少了你应用程序的内存印记,而且还能释放出更多的物理内存给其他系统进程 使用。 重要:在你中断你的空闲线程开始之前,你必须总是记录你应用程序当前的性能基线测量。 当你尝试修改后,采取额外的测量来确保你的修改实际上提高了性能,而不是对它操作损害。 1.5.3 避免共享数据结构 避免造成线程相关资源冲突的最简单最容易的办法是给你应用程序的每个线程 一份它需求的数据的副本。当最小化线程之间的通信和资源争夺时并行代码的效果最 好。 创建多线程的应用是很困难的。即使你非常小心,并且在你的代码里面所有正确 的地方锁住共享资源,你的代码依然可能语义不安全的。比如,当在一个特定的顺序 里面修改共享数据结构的时候,你的代码有可能遇到问题。以原子方式修改你的代码, 来弥补可能随后对多线程性能产生损耗的情况。把避免资源争夺放在首位通常可以得 到简单的设计同样具有高性能的效果。 1.5.4 多线程和你的用户界面 如果你的应用程序具有一个图形用户界面,建议你在主线程里面接收和界面相关 的事件和初始化更新你的界面。这种方法有助于避免与处理用户事件和窗口绘图相关 的同步问题。一些框架,比如 Cocoa,通常需要这样操作,但是它的事件处理可以不 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [9]
14 . Threading Programming Guide 这样做,在主线程上保持这种行为的优势在于简化了管理你应用程序用户界面的逻 辑。 有几个显著的例外,它有利于在其他线程执行图形操作。比如,QuickTime API 包含了一系列可以在辅助线程执行的操作,包括打开视频文件,渲染视频文件,压缩 视频文件,和导入导出图像。类似的,在 Carbon 和 Cocoa 里面,你可以使用辅助线 程来创建和处理图片和其他图片相关的计算。使用辅助线程来执行这些操作可以极大 提高性能。如果你不确定一个操作是否和图像处理相关,那么你应该在主线程执行这 些操作。 关于 QuickTime 线程安全的信息,查阅 Technical Note TN2125:“QuickTime 的 线程安全编程”。关于 Cocoa 线程安全的更多信息,查阅“线程安全总结”。关于 Cocoa 绘画信息,查阅 Cocoa 绘画指南(Cocoa Drawing Guide)。 1.5.5 了解线程退出时的行为 进程一直运行直到所有非独立线程都已经退出为止。默认情况下,只有应用程序 的主线程是以非独立的方式创建的,但是你也可以使用同样的方法来创建其他线程。 当用户退出程序的时候,通常考虑适当的立即中断所有独立线程,因为通常独立线程 所做的工作都是是可选的。如果你的应用程序使用后台线程来保存数据到硬盘或者做 其他周期行的工作,那么你可能想把这些线程创建为非独立的来保证程序退出的时候 不丢失数据。 以非独立的方式创建线程(又称作为可连接的)你需要做一些额外的工作。因为 大部分上层线程封装技术默认情况下并没有提供创建可连接的线程,你必须使用 POSIX API 来创建你想要的线程。此外,你必须在你的主线程添加代码,来当它们最 终退出的时候连接非独立的线程。更多有关创建可连接的线程信息,请查阅“设置线 程的脱离状态”部分。 如果你正在编程 Cocoa 的程序,你也可以通过使用 applicationShouldTerminate: 的委托方法来延迟程序的中断直到一段时间后或者完成取消。当延迟中断的时候,你 的程序需要等待直到任何周期线程已经完成它们的任务且调用了 replyToApplicationShouldTerminate:方法。关于更多这些方法的信息,请查阅 NSApplication Class Reference。 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [10]
15 . Threading Programming Guide 1.5.6 处理异常 当抛出一个异常时,异常的处理机制依赖于当前调用堆栈执行任何必要的清理。 因为每个线程都有它自己的调用堆栈,所以每个线程都负责捕获它自己的异常。如果 在辅助线程里面捕获一个抛出的异常失败,那么你的主线程也同样捕获该异常失败: 它所属的进程就会中断。你无法捕获同一个进程里面其他线程抛出的异常。 如果你需要通知另一个线程(比如主线程)当前线程中的一个特殊情况,你应该 捕捉异常,并简单地将消息发送到其他线程告知发生了什么事。根据你的模型和你正 在尝试做的事情,引发异常的线程可以继续执行(如果可能的话),等待指示,或者 干脆退出。 注意:在 Cocoa 里面,一个 NSException 对象是一个自包含对象,一旦它被引发了,那么它 可以从一个线程传递到另外一个线程。 在 一 些 情 况 下 , 异 常 处 理 可 能 是 自 动 创 建 的 。 比 如 , Objective-C 中 的 @synchronized 包含了一个隐式的异常处理。 1.5.7 干净地中断你的线程 线程自然退出的最好方式是让它达到其主入口结束点。虽然有不少函数可以用来 立即中断线程,但是这些函数应仅用于作为最后的手段。在线程达到它自然结束点之 前中断一个线程阻碍该线程清理完成它自己。如果线程已经分配了内存,打开了文件, 或者获取了其他类型资源,你的代码可能没办法回收这些资源,结果造成内存泄漏或 者其他潜在的问题。 关于更多正确退出线程的信息,请查阅“中断线程”部分。 1.5.8 线程安全的库 虽然应用程序开发人员控制应用程序是否执行多个线程,类库的开发者则无法这 样控制。当开发类库时,你必须假设调用应用程序是多线程,或者多线程之间可以随 时切换。因此你应该总是在你的临界区使用锁功能。 对类库开发者而言,只当应用程序是多线程的时候才创建锁是不明智的。如果你 需要锁定你代码中的某些部分,早期应该创建锁对象给你的类库使用,更好是显式调 用初始化类库。虽然你也可以使用静态库的初始化函数来创建这些锁,但是仅当没有 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [11]
16 . Threading Programming Guide 其他方式的才应该这样做。执行初始化函数需要延长加载你类库的时间,且可能对你 程序性能造成不利影响。 注意:永远记住在你的类库里面保持锁和释放锁的操作平衡。你应该总是记住锁定类库的数 据结构,而不是依赖调用的代码提供线程安全环境。 如果你真正开发 Cocoa 的类库,那么当你想在应用程序变成多线程的时候收到通 知的话,你可以给 NSWillBecomeMultiThreadedNotification 注册一个观察者。不 过你不应用依赖于这些收到的通知,因为它们可能在你的类库被调用之前已经被发出 了。 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [12]
17 . Threading Programming Guide 第二章 线程管理 Mac OS X 和 iOS 里面的每个进程都是有一个或多个线程构成,每个线程都代表一 个代码的执行路径。每个应用程序启动时候都是一个线程,它执行程序的 main 函数。 应用程序可以生成额外的线程,其中每个线程执行一个特定功能的代码。 当应用程序生成一个新的线程的时候,该线程变成应用程序进程空间内的一个实 体。每个线程都拥有它自己的执行堆栈,由内核调度独立的运行时间片。一个线程可 以和其他线程或其他进程通信,执行 I/O 操作,甚至执行任何你想要它完成的任务。 因为它们处于相同的进程空间,所以一个独立应用程序里面的所有线程共享相同的虚 拟内存空间,并且具有和进程相同的访问权限。 本章提供了 Mac OS X 和 iOS 上面可用线程技术的预览,并给出了如何在你的应 用程序里面使用它们的例子。 注意:获取关于 Mac OS 上面线程架构,或者更多关于线程的背景资料。请参阅技术说明 TN2028 --“线程架构”。 2.1 线程成本 多线程会占用你应用程序(和系统的)的内存使用和性能方面的资源。每个线程都 需要分配一定的内核内存和应用程序内存空间的内存。管理你的线程和协调其调度所 需的核心数据结构存储在使用 Wired Memory 的内核里面。你线程的堆栈空间和每个 线程的数据都被存储在你应用程序的内存空间里面。这些数据结构里面的大部分都是 当你首次创建线程或者进程的时候被创建和初始化的,它们所需的代价成本很高,因 为需要和内核交互。 表 2-1 量化了在你应用程序创建一个新的用户级线程所需的大致成本。这些成本 里面的部分是可配置的,比如为辅助线程分配堆栈空间的大小。创建一个线程所需的 时间成本是粗略估计的,仅用于当互相比较的时候。线程创建时间很大程度依赖于处 理器的负载,计算速度,和可用的系统和程序空间。 Table 2-1 Thread creation costs Item Approximate cost Notes Kernel data Approximately 1 This memory is used to store the thread data structures and attributes, structures KB much of which is allocated as wired memory and therefore cannot be 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [13]
18 . Threading Programming Guide paged to disk. 512 KB (secondary The minimum allowed stack size for secondary threads is 16 KB and the threads) Stack stack size must be a multiple of 4 KB. The space for this memory is set 8 MB (Mac OS X space aside in your process space at thread creation time, but the actual pages main thread) associated with that memory are not created until they are needed. 1 MB (iOS main thread) This value reflects the time between the initial call to create the thread and the time at which the thread’s entry point routine began executing. The Creation Approximately 90 figures were determined by analyzing the mean and median values time microseconds generated during thread creation on an Intel-based iMac with a 2 GHz Core Duo processor and 1 GB of RAM running Mac OS X v10.5. 注意:因为底层内核的支持,操作对象(Operation objectis)可能创建线程更快。它们使用内核 里面常驻线程池里面的线程来节省创建的时间,而不是每次都创建新的线程。关于更多使用操作 对象(Operation objects)的信息,参阅并发编程指南(Concurrency Programming Guide)。 当编写线程代码时另外一个需要考虑的成本是生产成本。设计一个线程应用程序 有时会需要根本性改变你应用程序数据结构的组织方式。要做这些改变可能需要避免 使用同步,因为本身设计不好的应用可能会造成巨大的性能损失。设计这些数据结构 和在线程代码里面调试问题会增加开发一个线程应用所需的时间。然而避免这些消耗 的话,可能在运行时候带来更大的问题,如果你的多线程花费太多的时间在锁的等待 而没有做任何事情。 2.2 创建一个线程 创建低级别的线程相对简单。在所有情况下,你必须有一个函数或方法作为线程 的主入口点,你必须使用一个可用的线程例程启动你的线程。以下几个部分介绍了比 较常用线程创建的基本线程技术。线程创建使用了这些技术的继承属性的默认设置, 由你所使用的技术来决定。关于更多如何配置你的线程的信息,参阅“线程属性配置” 部分。 2.2.1 使用 NSThread 使用 NSThread 来创建线程有两个可以的方法: 使用 detachNewThreadSelector:toTarget:withObject:类方法来生成一个 新的线程。 创建一个新的 NSThread 对象,并调用它的 start 方法。(仅在 iOS 和 Mac OS 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [14]
19 . Threading Programming Guide X v10.5 及其之后才支持) 这两种创建线程的技术都在你的应用程序里面新建了一个脱离的线程。一个脱离 的线程意味着当线程退出的时候线程的资源由系统自动回收。这也同样意味着之后不 需要在其他线程里面显式的连接(join)。因为 detachNewThreadSelctor:toTarget:withObject:方法在 Mac OS X 的任何版本都支 持,所以在 Cocoa 应用里面使用多线程的地方经常可以发现它。为了生成一个新的线 程,你只要简单的提供你想要使用为线程主体入口的方法的名称(被指定为一个 selector),和任何你想在启动时传递给线程的数据。下面的示例演示了这种方法的 基本调用,来使用当前对象的自定义方法来生成一个线程。 [NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil]; 在 Mac OS X v10.5 之前,你使用 NSThread 类来生成多线程。虽然你可以获取一 个 NSThread 对象并访问线程的属性,但你只能在线程运行之后在其内部做到这些。 在 Mac OS X v10.5 支持创建一个 NSThread 对象,而无需立即生成一个相应的新线程 (这些在 iOS 里面同样可用)。新版支持使得在线程启动之前获取并设置线程的很多 属性成为可能。这也让用线程对象来引用正在运行的线程成为可能。 在 Mac OS X v10.5 及其之后初始化一个 NSThread 对象的简单方法是使用 initWithTarget:selector:object:方法。该方法和 detachNewThreadSelector:toTarget:withObject:方法来初始化一个新的 NSThread 实例需要相同的额外开销。然而它并没有启动一个线程。为了启动一个线程,你可以 显式调用先对象的 start 方法,如下面代码: NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil]; [myThread start]; // Actually create the thread 注意:使用 initWithTarget:selector:object:方法的替代办法是子类化 NSThread,并重写 它的 main 方法。你可以使用你重写的该方法的版本来实现你线程的主体入口。更多信息,请参 阅 NSThread Class Reference 里面子类化的提示。 如果你拥有一个 NSThread 对象,它的线程当前真正运行,你可以给该线程发送 消息的唯一方法是在你应用程序里面的任何对象使用 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [15]
20 . Threading Programming Guide performSelector:onThread:withObject:waitUntilDone:方法。在 Mac OS X v10.5 支持在多线程上面执行 selectors(而不是在主线程里面),并且它是实现线程间通 信的便捷方法。你使用该技术时所发送的消息会被其他线程作为 run-loop 主体的一 部分直接执行(当然这些意味着目标线程必须在它的 run loop 里面运行,参阅“ Run Loops”)。当你使用该方法来实现线程通信的时候,你可能仍然需要一个同步操作, 但是这比在线程间设置通信端口简单多了。 注意:虽然在线程间的偶尔通信的时候使用该方法很好,但是你不能周期的或频繁的使用 performSelector:onThread:withObject:waitUntilDone:来实现线程间的通信。 关于线程间通信的可选方法,参阅“设置线程的脱离状态”部分。 2.2.2 使用 POSIX 的多线程 Mac OS X 和 iOS 提供基于 C 语言支持的使用 POSIX 线程 API 来创建线程的方法。 该技术实际上可以被任何类型的应用程序使用(包括 Cocoa 和 Cocoa Touch 的应用程 序),并且如果你当前真为多平台开发应用的话,该技术可能更加方便。你使用来创 建线程的 POSIX 例程被调用的时候,使用 pthread_create 刚好足够。 列表 2-1 显示了两个使用 POSIX 来创建线程的自定义函数。LaunchThread 函数创 建了一个新的线程,该线程的例程由 PosixThreadMainRoutine 函数来实现。因为 POSIX 创建的线程默认情况是可连接的(joinable),下面的例子改变线程的属性来创 建一个脱离的线程。把线程标记为脱离的,当它退出的时候让系统有机会立即回收该 线程的资源。 Listing 2-1 Creating a thread in C #include <assert.h> #include <pthread.h> void* PosixThreadMainRoutine(void* data) { // Do some work here. return NULL; } 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [16]
21 . Threading Programming Guide 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. } } 如果你把上面列表的代码添加到你任何一个源文件,并且调用 LaunchThread 函 数,它将会在你的应用程序里面创建一个新的脱离线程。当然,新创建的线程使用该 代码没有做任何有用的事情。线程将会加载并立即退出。为了让它更有兴趣,你需要 添加代码到 PosixThreadMainRoutine 函数里面来做一些实际的工作。为了保证线程 知道该干什么,你可以在创建的时候给线程传递一个数据的指针。把该指针作为 pthread_create 的最后一个参数。 为了在新建的线程里面和你应用程序的主线程通信,你需要建立一条和目标线程 之间的稳定的通信路径。对于基于 C 语言的应用程序,有几种办法来实现线程间的通 信,包括使用端口(ports),条件(conditions)和共享内存(shared memory)。对于 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [17]
22 . Threading Programming Guide 长期存在的线程,你应该几乎总是成立某种线程间的通信机制,让你的应用程序的主 线程有办法来检查线程的状态或在应用程序退出时干净关闭它。 关于更多介绍 POSIX 线程函数的信息,参阅 pthread 的主页。 2.2.3 使用 NSObject 来生成一个线程 在 iOS 和 Mac OS X v10.5 及其之后,所有的对象都可能生成一个新的线程,并 用它来执行它任意的方法。方法 performSelectorInBackground:withObject:新生成 一个脱离的线程,使用指定的方法作为新线程的主体入口点。比如,如果你有一些对 象(使用变量 myObj 来代表),并且这些对象拥有一个你想在后台运行的 doSomething 的方法,你可以使用如下的代码来生成一个新的线程: [myObj performSelectorInBackground:@selector(doSomething) withObject:nil]; 调用该方法的效果和你在当前对象里面使用 NSThread 的 detachNewThreadSelector:toTarget:withObject:传递 selectore,object 作为参数 的方法一样。新的线程将会被立即生成并运行,它使用默认的设置。在 selectore 内部,你必须配置线程就像你在任何线程里面一样。比如,你可能需要设置一个自动 释放池(如果你没有使用垃圾回收机制),在你要使用它的时候配置线程的 run loop。 关于更是介绍如果配置线程的信息,参阅“配置线程属性”部分。 2.2.4 使用其他线程技术 尽管 POSIX 例程和 NSThread 类被推荐使用来创建低级线程,但是其他基于 C 语 言的技术在 Mac OS X 上面同样可用。在这其中,唯一一个可以考虑使用的是多处理 服务(Multiprocessing Services),它本身就是在 POSIX 线程上执行。多处理服务 是专门为早期的 Mac OS 版本开发的,后来在 Mac OS X 里面的 Carbon 应用程序上面 同样适用。如果你有代码真是有该技术,你可以继续使用它,尽管你应该把这些代码 转化为 POSIX。该技术在 iOS 上面不可用。 关于更多如何使用多处理服务的信息,参阅多处理服务编程指南 (Multiprocessing Services Programming Guide)。 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [18]
23 . Threading Programming Guide 2.2.5 在 Cocoa 程序上面使用 POSIX 线程 经管 NSThread 类是 Cocoa 应用程序里面创建多线程的主要接口,如果可以更方 便的话你可以任意使用 POSIX 线程带替代。例如,如果你的代码里面已经使用了它, 而你又不想改写它的话,这时你可能需要使用 POSIX 多线程。如果你真打算在 Cocoa 程序里面使用 POSIX 线程,你应该了解如果在 Cocoa 和线程间交互,并遵循以下部分 的一些指南。 Cocoa 框架的保护 对于多线程的应用程序,Cocoa 框架使用锁和其他同步方式来保证代码的正确执 行。为了保护这些锁造成在单线程里面性能的损失, Cocoa 直到应用程序使用 NSThread 类生成它的第一个新的线程的时候才创建这些锁。如果你仅且使用 POSIX 例程来生成新的线程,Cocoa 不会收到关于你的应用程序当前变为多线程的通知。当 这些刚好发生的时候,涉及 Cocoa 框架的操作哦可能会破坏甚至让你的应用程序崩 溃。 为了让 Cocoa 知道你正打算使用多线程,你所需要做的是使用 NSThread 类生成 一个线程,并让它立即退出。你线程的主体入口点不需要做任何事情。只需要使用 NSThread 来生成一个线程就足够保证 Cocoa 框架所需的锁到位。 如果你不确定 Cocoa 是否已经知道你的程序是多线程的,你可以使用 NSThread 的 isMultiThreaded 方法来检验一下。 混合 POSIX 和 Cocoa 的锁 在同一个应用程序里面混合使用 POSIX 和 Cocoa 的锁很安全。Cocoa 锁和条件对 象基本上只是封装了 POSIX 的互斥体和条件。然而给定一个锁,你必须总是使用同样 的接口来创建和操纵该锁。换言之,你不能使用 Cocoa 的 NSLock 对象来操纵一个你 使用 pthread_mutex_init 函数生成的互斥体,反之亦然。 2.3 配置线程属性 创建线程之后,或者有时候是之前,你可能需要配置不同的线程环境。以下部分 描述了一些你可以做的改变,和在什么时候你需要做这些改变。 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [19]
24 . Threading Programming Guide 2.3.1 配置线程的堆栈大小 对于每个你新创建的线程,系统会在你的进程空间里面分配一定的内存作为该线 程的堆栈。该堆栈管理堆栈帧,也是任何线程局部变量声明的地方。给线程分配的内 存大小在“线程成本”里面已经列举了。 如果你想要改变一个给定线程的堆栈大小,你必须在创建该线程之前做一些操 作。所有的线程技术提供了一些办法来设置线程堆栈的大小。虽然可以使用 NSThread 来设置堆栈大小,但是它只能在 iOS 和 Mac OS X v10.5 及其之后才可用。表 2-2 列 出了每种技术的对于不同的操作。 Table 2-2 Setting the stack size of a thread Technology Option In iOS and Mac OS X v10.5 and later, allocate and initialize an NSThread object (do not use thedetachNewThreadSelector:toTarget:withObject: method). Before calling the Cocoa start method of the thread object, use thesetStackSize: method to specify the new stack size. Create a new pthread_attr_t structure and use the pthread_attr_setstacksize POSIX function to change the default stack size. Pass the attributes to the pthread_create function when creating your thread. Multiprocessing Pass the appropriate stack size value to the MPCreateTask function when you create your Services thread. 2.3.2 配置线程本地存储 每个线程都维护了一个键-值的字典,它可以在线程里面的任何地方被访问。你 可以使用该字典来保存一些信息,这些信息在整个线程的执行过程中都保持不变。比 如,你可以使用它来存储在你的整个线程过程中 Run loop 里面多次迭代的状态信息。 Cocoa 和 POSIX 以不同的方式保存线程的字典,所以你不能混淆并同时调用者两 种技术。然而只要你在你的线程代码里面坚持使用了其中一种技术,最终的结果应该 是一样的。在 Cocoa 里面,你使用 NSThread 的 threadDictionary 方法来检索一个 NSMutableDictionary 对象,你可以在它里面添加任何线程需要的键。在 POSIX 里面, 你使用 pthread_setspecific 和 pthread_getspecific 函数来设置和访问你线程的键 和值。 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [20]
25 . Threading Programming Guide 2.3.3 设置线程的脱离状态 大部分上层的线程技术都默认创建了脱离线程(Datached thread)。大部分情况 下,脱离线程(Detached thread)更受欢迎,因为它们允许系统在线程完成的时候立 即释放它的数据结构。脱离线程同时不需要显示的和你的应用程序交互。意味着线程 检索的结果由你来决定。相比之下,系统不回收可连接线程(Joinable thread)的 资源直到另一个线程明确加入该线程,这个过程可能会阻止线程执行加入。 你可以认为可连接线程类似于子线程。虽然你作为独立线程运行,但是可连接线 程在它资源可以被系统回收之前必须被其他线程连接。可连接线程同时提供了一个显 示的方式来把数据从一个正在退出的线程传递到其他线程。在它退出之前,可连接线 程可以传递一个数据指针或者其他返回值给 pthread_exit 函数。其他线程可以通过 pthread_join 函数来拿到这些数据。 重要:在应用程序退出时,脱离线程可以立即被中断,而可连接线程则不可以。每个可连接 线程必须在进程被允许可以退出的时候被连接。所以当线程处于周期性工作而不允许被中断的时 候,比如保存数据到硬盘,可连接线程是最佳选择。 如果你想要创建可连接线程,唯一的办法是使用 POSIX 线程。POSIX 默认创建的 线程是可连接的。为了把线程标记为脱离的或可连接的,使用 pthread_attr_setdetachstate 函数来修改正在创建的线程的属性。在线程启动后, 你可以通过调用 pthread_detach 函数来把线程修改为可连接的。关于更多 POSIX 线 程函数信息,参与 pthread 主页。关于更多如果连接一个线程,参阅 pthread_join 的主页。 2.3.4 设置线程的优先级 你创建的任何线程默认的优先级是和你本身线程相同。内核调度算法在决定该运 行那个线程时,把线程的优先级作为考量因素,较高优先级的线程会比较低优先级的 线程具有更多的运行机会。较高优先级不保证你的线程具体执行的时间,只是相比较 低优先级的线程,它更有可能被调度器选择执行而已。 重要:让你的线程处于默认优先级值是一个不错的选择。增加某些线程的优先级,同时有可 能增加了某些较低优先级线程的饥饿程度。如果你的应用程序包含较高优先级和较低优先级线 程,而且它们之间必须交互,那么较低优先级的饥饿状态有可能阻塞其他线程,并造成性能瓶颈。 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [21]
26 . Threading Programming Guide 如果你想改变线程的优先级,Cocoa 和 POSIX 都提供了一种方法来实现。对于 Cocoa 线程而言,你可以使用 NSThread 的 setThreadPriority:类方法来设置当前运 行线程的优先级。对于 POSIX 线程,你可以使用 pthread_setschedparam 函数来实现。 关于更多信息,参与 NSThread Class Reference 或 pthread_setschedparam 主页。 2.4 编写你线程的主体入口点 对于大部分而言,Mac OS X 上面线程结构的主体入口点和其他平台基本一样。你 需要初始化你的数据结构,做一些工作或可行的设置一个 run loop,并在线程代码 被执行完后清理它。根据设计,当你写的主体入口点的时候有可能需要采取一些额外 的步骤。 2.4.1 创建一个自动释放池(Autorelease Pool) 在 Objective - C 框架链接的应用程序,通常在它们的每一个线程必须创建至少 一个自动释放池。如果应用程序使用管理模型,即应用程序处理的 retain 和 release 对象,那么自动释放池捕获任何从该线程 autorelease 的对象。 如果应用程序使用的垃圾回收机制,而不是管理的内存模型,那么创建一个自动 释放池不是绝对必要的。在垃圾回收的应用程序里面,一个自动释放池是无害的,而 且大部分情况是被忽略。允许通过个代码管理必须同时支持垃圾回收和内存管理模 型。在这种情况下,内存管理模型必须支持自动释放池,当应用程序运行垃圾回收的 时候,自动释放池只是被忽略而已。 如果你的应用程序使用内存管理模型,在你编写线程主体入口的时候第一件事情 就是创建一个自动释放池。同样,在你的线程最后应该销毁该自动释放池。该池保证 自动释放。虽然对象被调用,但是它们不被 release 直到线程退出。列表 2-2 显示了 线程主体入口使用自动释放池的基本结构。 Listing 2-2 Defining your thread entry point routine - (void)myThreadMainRoutine { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool // Do thread work here. 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [22]
27 . Threading Programming Guide [pool release]; // Release the objects in the pool. } 因为高级的自动释放池不会释放它的对象直到线程退出。长时运行的线程需求新 建额外的自动释放池来更频繁的释放它的对象。比如,一个使用 run loop 的线程可 能在每次运行完一次循环的时候创建并释放该自动释放池。更频繁的释放对象可以防 止你的应用程序内存占用太大造成性能问题。虽然对于任何与性能相关的行为,你应 该测量你代码的实际表现,并适当地调整使用自动释放池。 关于更多内存管理的信息和自动释放池,参阅“内存高级管理编程指南(Advanced Memory Management Programming Guide)”。 2.4.2 设置异常处理 如果你的应用程序捕获并处理异常,那么你的线程代码应该时刻准备捕获任何可 能发生的异常。虽然最好的办法是在异常发生的地方捕获并处理它,但是如果在你的 线程里面捕获一个抛出的异常失败的话有可能造成你的应用程序强退。在你线程的主 体入口点安装一个 try/catch 模块,可以让你捕获任何未知的异常,并提供一个合适 的响应。 当在 Xcode 构建你项目的时候,你可以使用 C++或者 Objective-C 的异常处理风 格。 关于更多设置如何在 Objective-C 里面抛出和捕获异常的信息,参阅 Exception Programming Topics。 2.4.3 设置一个 Run Loop 当你想编写一个独立运行的线程时,你有两种选择。第一种选择是写代码作为一 个长期的任务,很少甚至不中断,线程完成的时候退出。第二种选择是把你的线程放 入一个循环里面,让它动态的处理到来的任务请求。第一种方法不需要在你的代码指 定任何东西;你只需要启动的时候做你打算做的事情即可。然而第二种选择需要在你 的线程里面添加一个 run loop。 Mac OS X 和 iOS 提供了在每个线程实现 run loop 内置支持。Cocoa、Carbon 和 UIKit 自动在你应用程序的主线程启动一个 run loop,但是如果你创建任何辅助线程, 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [23]
28 . Threading Programming Guide 你必须手工的设置一个 run loop 并启动它。 关于更多使用和配置 run loop 的信息,参阅“Run Loops”部分。 2.5 中断线程 退出一个线程推荐的方法是让它在它主体入口点正常退出。经管 Cocoa、POSIX 和 Multiprocessing Services 提供了直接杀死线程的例程,但是使用这些例程是强 烈不鼓励的。杀死一个线程阻止了线程本身的清理工作。线程分配的内存可能造成泄 露,并且其他线程当前使用的资源可能没有被正确清理干净,之后造成潜在的问题。 如果你的应用程序需要在一个操作中间中断一个线程,你应该设计你的线程响应 取消或退出的消息。对于长时运行的操作,这意味着周期性停止工作来检查该消息是 否到来。如果该消息的确到来并要求线程退出,那么线程就有机会来执行任何清理和 退出工作;否则,它返回继续工作和处理下一个数据块。 响应取消消息的一个方法是使用 run loop 的输入源来接收这些消息。列表 2-3 显示了该结构的类似代码在你的线程的主体入口里面是怎么样的(该示例显示了主循 环部分,不包括设立一个自动释放池或配置实际的工作步骤)。该示例在 run loop 上面安装了一个自定义的输入源,它可以从其他线程接收消息。关于更多设置输入源 的信息,参阅“配置 Run Loop 源”。执行工作的总和的一部分后,线程运行的 run loop 来查看是否有消息抵达输入源。如果没有,run loop 立即退出,并且循环继续处理 下一个数据块。因为该处理器并没有直接的访问 exitNow 局部变量,退出条件是通过 线程的字典来传输的。 Listing 2-3 Checking for an exit condition during a long job - (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] 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [24]
29 . Threading Programming Guide 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]; } } 2011-11-28 | © 2011 YouMi Mobile Co. Ltd. All Rights Reserved. [25]