多线程编程指南(官方文档)三

来源:互联网 发布:合肥淘宝包装招聘 编辑:程序博客网 时间:2024/05/17 18:18
线程设计建议
以下内容为多线程设计的指导,可以帮助你正确实现多线程代码。其中还包涵了一些提高线程性能的建议。和其他建议中提到的一样,在修改代码之前,修改代码时和修改后收集相关性能报表。


避免直接创建线程
手 动编写创建线程的代码费时费力,且易出隐含错误,因此应尽可能避免这样做。Mac OS X和iOS通过其他API提供了对并行间接的支持。可以考虑一下使用异步的API, G~C~D或是操作对象来实现任务。这些技术可以帮助你完成线程相关的工作,你不需要考虑内部的复杂性,而且系统保证这些操作是正确的。另外,像 G~C~D和operation object这些技术被设计用来更高效的管理线程,通过衡量当前的系统负载来调整活跃线程个数,其效率远远高于自己通过代码来实现。 更多关于G~C~D和operation objects的内容,请参考Concurrency Programming Guide


保持线程适度的忙状态
在 手动创建、管理线程时,时刻谨记线程是会消耗宝贵的系统资源的。你要尽力保证线程的工作是长期的、有效的。同时,对于那些长时间出于空闲状态的线程,要毫 不犹豫的处理掉。线程会占用大量的内存,所以释放空闲线程不只是减少你自己程序的内存消耗,也会为其他系统进程节省更多物理内存空间。


重要信息:在结束空闲线程之前,你应该对当前程序的性能做一个基本的记录。在修改之后,要确保改动是提高性能,而不是影响性能。


避免共享数据结构
避免线程相关资源冲突最简单直接的办法,就是给每个线程一份数据的拷贝。当你将线程之间的通信和资源竞争减到最小程度的时候,并行代码会工作的更好。


创 建多线程的程序并不容易。即使你非常小心,在所有适当的地方锁住了共享数据,你的代码在语义上可能还是不安全的。例如,在你想要以某种书序修改一份共享数 据时,就可能遇到问题。若为了解决这样的问题,使用基于事务的模型,又可能导致性能下降,而背离了使用多线程的初衷。在一开始就减少资源竞争,既可以简化 设计,又能够获得好的性能。


线程和UI
如果你的程序拥有图形界面,建议在主线程中实现用户事件的接收,以及界面的刷新等动作。这 样做可以避免绘制窗口内容和事件处理方面的同步问题。某些framework,例如Cocoa,通常会要求你这样做,但即便是没有要求,在主线程中做这些 事情也会让你受益,它简化了管理UI的逻辑。


但是,有几个明显的反例。在这些例子里,在线程中执行绘图任务反而是有好处的。例 如,QuickTime的API中包括了几个可以在子线程中执行的操作,如打开电影文件、绘制电影文件、压缩电影文件、导入和导出图片。同样,在 Carbon和Cocoa中,你可以在线程内部创建、处理图片或做其他和图片相关的计算。这样可以大幅度提升性能。如果你不确定某个图像操作是否可以这 样,还是老实的在主线程中执行吧。


更多关于QuickTime的线程安全知识,参考Technical Note TN2125:"Thread-Safe Programming in QuickTime".更多关于Cocoa线程安全性的信息,参考“Thread Safety Summary.” 更多关于Cocoa中绘制方面的信息,参考Cocoa Drawing Guide。


了解退出时线程的行为
当进程中所有非分离的 (nondetached)线程退出后,进程结束。默认状态下,只有程序的主线程被创建成为这种。但是你也可以用这种方式创建子线程。当用户结束程序时, 通常情况下,立即结束所有分离的(detached)线程是合适的。因为分离的线程完成的任务一般不是最重要的。但是,当你的程序使用后台线程来存储数据 或是其他重要工作时,你可能需要创建非分离的线程,来保证在程序退出的时候不丢失数据。


创建非分离的(也被叫做“可会合的”)线程需要额 外的工作。因为很多高层次的线程技术默认是不会按这种方式创建的。你可能需POSIX API来创建自己的线程。另外,你必须在主线程中编写代码,在程序退出时,执行子线程和主线程会合的操作。更多创建可会合的线程方面的信息,参考 “Setting the Detached State of a Thread.”


如果你要编写Cocoa程序,你也可以用 applicationShouldTerminate: 的delegate方法来延迟程序的退出,或者把线程操作一并取消(该句待校准)。当延迟退出时,你的程序需要等待重要线程的完成,在其完成后调用 replyToApplicationShouldTerminate:方法,更多信息,请参考NSApplication Class Reference。


处理异常
异常处理机制基于当前的调用栈(call stack),在有异常被抛出时,执行必要的清除操作。由于每个线程都有独立的调用栈,因此需要各自负责捕获异常。在子线程捕获异常失败的后果和在主线程中一样:进程会终结。不能将未捕获的异常抛给另外的线程来处理。


如果你需要告知另外的线程(如主线程)当前线程中的异常状态,你应该先捕获该异常,然后发送一条消息告诉那个线程出问题了。根据你的设计和需要,捕获异常的线程可以继续执行(如果可能的话)、等待指令或是退出。


注意:在Cocoa中,NSException对象是一个独立、完整的对象,在捕获后,可以在线程间传递。


在某些情况下,异常处理机制常自动添加。例如,Objective-C中的@synchronized指令就包含了隐含的异常处理。


“干净”地解决掉线程
最 好的方式是在到达程序主入口的终点时,自然结束掉线程。另外,我们还有方法来立即结束线程,除非不得以,不应使用那些方法。在线程“自然死”之前,强制结 束它会影响线程的清理工作。如果线程申请了内存,打开过文件,或是请求过其他类型的资源,你的代码将无法回收那些资源,从而导致内存泄露或其他隐含的问 题。


更多关于正确结束线程的信息,参考“Terminating a Thread.”


库中的线程安全性
程序开发者可以控制是否使用多线程,但库开发者就没有这个自由了。当开发一个库的时候,你必须假定调用库的程序使用了多线程或可以在任何时候切换到多线程模式。因此,必须一直使用锁来保护临界区代码。


对 于库开发者来说,仅在程序转多线程模式时使用锁是不明智的。如果你需要在某个点上锁住代码操作,在使用库之前创建锁对象,最好调用隐式代码来初始化库(待 校准。。。)。尽管你也可以使用静态库的初始化方法来创建这样的锁对象,但不到不得已,不要这样做。执行初始化方法会增加加载库的时间,从而明显影响性 能。


注意:始终牢记在库中平衡锁和解锁的操作。你还应该注意锁住库中的数据,而不是靠调用代码来提供一个线程安全的环境。


如 果你要开发Cocoa的库,你可以注册一个观察者对象(observer),针对键名为 NSWillBecomeMultiThreadedNotification。这样在程序变为多线程方式时,你就能收到通知。但不能依赖于这个通知,因 为它可能在你库中的代码被调用前被发送出来。
原创粉丝点击