在Cocoa上面使用多线程的指南包括如下这些:html
(1)不可改变的对象通常是线程安全的。一旦你建立了它们,你能够把这些对象在线程间安全的传递。另外一方面,可变对象一般不是线程安全的。为了在多线程应用里面使用可变对象,应用必须适当的同步。关于更多信息,参阅”可变和不可变对比”。ios
(2)许多对象在多线程里面不安全的使用被视为是”线程不安全的”。只要同一时间只有一个线程,那么许多这些对象能够被多个线程使用。这种被称为专门限制应用程序的主线程的对象一般被这样调用。编程
(3)应用的主线程负责处理事件。尽管Application Kit在其余线程被包含在事件路径里面时还会继续工做,但操做可能会被打乱顺序。windows
(4)若是你想使用一个线程来绘画一个视图,把全部绘画的代码放在NSView的lockFocusIfCanDraw和unlockFocus方法中间。数组
为了在Cocoa里面使用POSIX线程,你必须首先把Cocoa变为多线程模式。关于更多信息,参阅“在Cocoa应用里面使用POSIX线程”部分。缓存
有一种误解,认为基础框架(Foundation framework)是线程安全的,而Application Kit是非线程安全的。不幸的是,这是一个总的归纳,从而形成一点误导。每一个框架都包含了线程安所有分和非线程安所有分。如下部分介绍Foundation framework里面的线程安所有分。安全
线程安全的类和函数服务器
下面这些类和函数一般被认为是线程安全的。你能够在多个线程里面使用它们的同一个实例,而无需获取一个锁。数据结构
非线程安全类多线程
如下这些类和函数一般被认为是非线程安全的。在大部分状况下,你能够在任何线程里面使用这些类,只要你在同一个时间只在一个线程里面使用它们。参考这些类对于的额外详细信息的文档。
注意,尽管NSSerializer,NSArchiver,NSCoder和NSEnumerator对象自己是线程安全的,可是它们被放置这这里是由于当它们封装的对象被使用的时候,更改这些对象数据是不安全的。好比,在归档状况下,修改被归档的对象是不安全的。对于一个枚举,任何线程修改枚举的集合都是不安全的。
只能用于主线程的类
如下的类必须只能在应用的主线程类使用。
可变 vs 不可变
不可变对象一般是线程安全的。一旦你建立了它们,你能够把它们安全的在线程间传递。当前,在使用不可变对象时,你还应该记得正确使用引用计数。若是不适当的释放了一个你没有引用的对象,你在随后有可能形成一个异常。
可变对象一般是非线程安全的。为了在多线程应用里面使用可变对象,应用应该使用锁来同步访问它们(关于更多信息,参见“原子操做”部分)。一般状况下,集合类(好比,NSMutableArray,NSMutableDictionary)是考虑多变时是非线程安全的。这意味着,若是一个或多个线程同时改变一个数组,将会发生问题。你应该在线程读取和写入它们的地方使用锁包围着。
即便一个方法要求返回一个不可变对象,你不该该简单的假设返回的对象就是不可变的。依赖于方法的实现,返回的对象有多是可变的或着不可变的。好比,一个返回类型是NSString的方法有可能实际上因为它的实现返回了一个NSMutableString。若是你想要确保对象是不可变的,你应该使用不可变的拷贝。
可重入性
可重入性是可让同一对象或者不一样对象上一个操做“调用”其余操做成为可能。保持和释放对象就是一个有可能被忽视的”调用”的例子。
如下列表列出了Foundation framework的部分显式的可重入对象。全部其余类多是或可能不是可重入的,或者它们未来有多是可重入的。对于可重入性的一个完整的分析是不可能完成的,并且该列表将会是无穷尽的。
类的初始化
Objective-C的运行时系统在类收到其余任何消息以前给它发送一个initialize消息。这可让类有机会在它被使用前设置它的运行时环境。在一个多线程应用里面,运行时保证仅有一个线程(该线程刚好发送第一条消息给类)执行initialized方法,第二个线程阻塞直到第一个线程的initialize方法执行完成。在此期间,第一个线程能够继续调用其余类上的方法。该initialize方法不该该依赖于第二个线程对这个类的调用。若是不是这样的话,两个线程将会形成死锁。
自动释放池(Autorelease Pools)
每一个线程都维护它本身的NSAutoreleasePool的栈对象。Cocoa但愿在每一个当前线程的栈里面有一个可用的自动释放池。若是一个自动释放池不可用,对象将不会给释放,从而形成内存泄露。对于Application Kit的主线程一般它会自动建立并消耗一个自动释放池,可是辅助线程(和其余只有Foundationd的程序)在使用Cocoa前必须本身手工建立。若是你的线程是长时间运行的,那么有可能潜在产生不少自动释放的对象,你应该周期性的销毁它们并建立自动释放池(就像Application Kit对主线程那样)。不然,自动释放对象将会积累并形成内存大量占用。若是你的脱离线程没有使用Cocoa,你不须要建立一个自动释放池。
Run Loops
每一个线程都有一个或多个run loop。然而每一个run loop和每一个线程都有它本身的输入模式来决定run loop运行的释放监听那些输入源。输入模式定义在一个run loop上面,不会影响定义在其余run loop的输入模式,即便它们的名字相同。
若是你的线程是基于Application Kti的话,主线程的run loop会自动运行,可是辅助线程(和只有Foundation的应用)必须本身启动它们的run loop。若是一个脱离线程没有进入run loop,那么线程在完成它们的方法执行后会当即退出。
尽管外表显式多是线程安全的,可是NSRunLoop类是非线程安全的。你只能在拥有它们的线程里面调用它实例的方法。
如下部分介绍了Application Kit框架的线程安全。
非线程安全类
如下这些类和函数一般是非线程安全的。大部分状况下,你能够在任何线程使用这些类,只要你在同一时间只有一个线程使用它们。查看这些类的文档来得到更多的详细信息。
只能用于主线程的类
如下的类必须只能在应用的主线程使用。
Window 限制
你能够在辅助线程建立一个window。Application Kit确保和window相关的数据结构在主线程释放来避免产生条件。在同时包含大量windows的应用中,window对象有可能会发生泄漏。
你也能够在辅助线程建立modal window。在主线程运行modal loop时,Application Kit阻塞辅助线程的调用。
事件处理例程限制
应用的主线程负责处理事件。主线程阻塞在NSApplication的run方法,一般该方法被包含在main函数里面。在Application Kit继续工做时,若是其余线程被包含在事件路径,那么操做有可能打乱顺序。好比,若是两个不一样的线程负责关键事件,那么关键事件有可能不是按照顺序到达。经过让主线程来处理事件,事件能够被分配到辅助线程由它们处理。
你能够在辅助线程里面使用NSApplication的postEvent:atStart方法传递一个事件给主线程的事件队列。然而,顺序不能保证和用户输入的事件顺序相同。应用的主线程仍然辅助处理事件队列的事件。
绘画限制
Application Kit在使用它的绘画函数和类时一般是线程安全的,包括NSBezierPath和NSString类。关于使用这些类的详细信息,在如下各部分介绍。关于绘画的额外信息和线程能够查看Cocoa Drawing Guide。
a) NSView限制
NSView一般是线程安全的,包含几个异常。你应该仅在应用的主线程里面执行对NSView的建立、销毁、调整大小、移动和其余操做。在其余辅助线程里面只要你把绘画的代码放在lockFocusIfCanDraw和unlockFocus方法之间也是线程安全的。
若是应用的辅助线程想要告知主线程重绘视图,必定不能在辅助线程直接调用display,setNeedsDisplay:,setNeedsDisplayInRect:,或setViewsNeedDisplay:方法。相反,你应该给给主线程发生一个消息让它调用这些方法,或者使用performSelectorOnMainThread:withObject:waitUntilDone:方法。
系统视图的图形状态(gstates)是基于每一个线程不一样的。使用图形状态能够在单线程的应用里面得到更好的绘画性能,可是如今已经不是这样了。不正确使用图形状态可能致使主线程的绘画代码更低效。
b) NSGraphicsContext 限制
NSGraphicsContext类表明了绘画上下文,它由底层绘画系统提供。每一个NSGraphicsContext实例都拥有它独立的绘画状态:坐标系统、裁剪、当前字体等。该类的实例在主线程自动建立本身的NSWindow实例。若是你在任何辅助线程执行绘画操做,须要特定为该线程建立一个新的NSGraphicsContext实例。
若是你在任何辅助线程执行绘画,你必须手工的刷新绘画调用。Cocoa不会自动更新辅助线程绘画的内容,因此你当你完成绘画后须要调用NSGraphicsContext的flusGrahics方法。若是你的应用程序只在主线程绘画,你不须要刷新绘画调用。
c) NSImage限制
线程能够建立NSImage对象,把它绘画到图片缓冲区,还能够把它传递给主线程来绘画。底层的图片缓存被全部线程共享。关于图片和如何缓存的更多信息,参阅Ccocoa Drawing Guide。
Core Data框架一般支持多线程,尽管须要注意一些使用注意事项。关于这些注意事项的更多信息,参阅Core Data Programing Guide的“Multi-Threading with Core Data”部分。
Core Foundation是足够线程安全的,若是你的程序注意一下的话,应该不会遇到任何线程竞争的问题。一般状况下是线程安全的,好比当你查询(query)、引用(retain)、释放(release)和传递(pass)不可变对象时。甚至在多个线程查询中央共享对象也是线程安全的。
像Cocoa那样,当涉及对象或它们内容突变时,Core Foundation是非线程安全的。好比,正如你所指望的,不管修改一个可变数据或可变数组对象,仍是修改一个可变数组里面的对象都是非线程安全的。其中一个缘由是性能,这是在这种状况下的关键。此外,在该级别上实现彻底线程安全是几乎不可能的。例如,你不能排除从集合中引用(retain)一个对象产生的没法肯定的结果。该集合自己在被调用来引用(retain)它所包含的对象以前有可能已经被释放了。
这些状况下,当你的对象被多个线程访问或修改,你的代码应该在相应的地方使用锁来保护它们不要被同时访问。例如,枚举Core Foundation数组对象的代码,在枚举块代码周围应该使用合适的锁来保护它免遭其余线程修改。
应用(application)
一个显示一个图形用户界面给用户的特定样式程序。
条件(condition)
一个用来同步资源访问的结构。线程等待某一条件来决定是否被容许继续运行,直到其余线程显式的给该条件发送信号。
临界区(critical section)
同一时间只能不被一个线程执行的代码。
输入源(input source)
一个线程的异步事件源。输入源能够是基于端口的或手工触发,而且必须被附加到某一个线程的run loop上面。
可链接的线程(join thread)
退出时资源不会被当即回收的线程。可链接的线程在资源被回收以前必须被显式脱离或由其余线程链接。可链接线程提供了一个返回值给链接它的线程。
主线程(main thread)
当建立进程时一块儿建立的特定类型的线程。当程序的主线程退出,则程序即退出。
互斥锁(mutex)
提供共享资源互斥访问的锁。一个互斥锁同一时间只能被一个线程拥有。试图获取一个已经被其余线程拥有的互斥锁,会把当前线程置于休眠状态知道该锁被其余线程释放并让当前线程得到。
操做对象(operation object)
NSOperation类的实例。操做对象封装了和某一任务相关的代码和数据到一个执行单元里面。
操做队列(operation queue)
NSOperationQueue类的实例。操做队列管理操做对象的执行。
进程(process)
应用或程序的运行时实例。一个进程拥有独立于分配给其余程序的的内存空间和系统资源(包括端口权限)。进程老是包含至少一个线程(即主线程)和任意数量的额外线程。
程序(program)
能够用来执行某些任务的代码和资源的组合。程序不须要一个图形用户界面,尽管图形应用也被称为程序。
递归锁(recursive lock)
能够被同一线程屡次锁住的锁。
Run loop(运行循环)
一个事件处理循环,在此期间事件被接收并分配给合适的处理例程。
Run loop模式(run loop mode)
与某一特定名称相关的输入源、定时源和run loop观察者的集合。当运行在某一特定“模式”下,一个run loop监视和该模式相关的源和观察者。
Run loop对象(run loop object)
NSRunLoop类或CFRunLoopRef不透明类型的实例。这些对象提供线程里面实现事件处理循环的接口。
Run loop观察者(run loop observer)
在run loop运行的不一样阶段时接收通知的对象。
信号量(semaphore)
一个受保护的变量,它限制共享资源的访问。互斥锁(mutexes)和条件(conditions)都是不一样类型的信号量。
任务(task)
要执行的工做数量。尽管一些技术(最显著的是Carbon 多进程服务—Carbon Multiprocessing Services)使用该术语的意义有时不一样,可是最通用的用法是代表须要执行的工做数量的抽象概念。
线程(thread)
进程里面的一个执行过程流。每一个线程都有它本身的栈空间,但除此以外同一进程的其余线程共享内存。
定时源(timer source)
为线程同步事件的源。定时器产生预约时间将要执行的一次或重复事件。
多线程编程在开发应用的时候很是有帮助。好比你能够在后台加载图片,等图片加载完成后再在主线程更新等,或者在后台处理一些须要占用CPU很长时间的事件(好比请求服务器,加载数据等)。要体会多线程编程的好处,还得多实战,结合使用多种多线程技术。特别要注意Run Loop的使用,不少开发者在编写多线程应用的时候不多关注过Run Loop。若是你仔细阅读并掌握Run Loop的细节,将会帮助你写出更优美的代码。同步是多线程编程的老生常谈,估计大学时候你们都基本熟悉了同步的重要性。
最后,本文在翻译过程当中发现不少地方直译成中文比较晦涩,因此采用了意译的方式,这不可避免的形成有一些地方可能和原文有必定的出入,因此若是你阅读的时候发现有任何的错误均可以讨论指正。