线程安全总结(ThreadSafety Summary)

本附录描述OS X和iOS中高级线程安全的一些关键框架。本附录中的信息可能发生变化。

Cocoa

Cocoa中多线程使用指南包括以下几点:

不可变对象通常是线程安全的。一旦你创建它们,你可以在线程间安全的传递这些对象。另一方面,可变的对象通常是线程不安全的。在线程应用中使用可变对象,应用必须适当的同步。更多信息,参见可变VS不可变(Mutable Versus Immutable)。

许多视为“线程不安全”的对象只是在多线程中使用不安全。只要一次只有一个线程,很多这些对象可以使用在任何线程中。应用的主线程明确限制调用这类对象。

应用程序的主线程负责处理事件。尽管其他线程包含在事件路径中,应用工具包继续工作,但操作顺序会不对。

如果你想使用线程来绘制视图,把代码放在NSViewlockFocusIfCanDraw 和unlockFocus 方法之间。

Cocoa中使用POSIX线程,你必须先把Cocoa设为多线程模式。更多信息,参见在Cocoa应用中使用POSIX线程( Using POSIX Threads in a Cocoa Application)。

基础框架线程安全

有一种误解,认为基础框架是线程安全的,应用工具包框架是线程不安全的。不幸的是,这是一个粗略的概括,有些误导。每个框架都有些区域是线程安全的有些区域是线程不安全的。以下章节描述一般线程安全的基础框架。

线程安全的类和函数

以下类和函数通常被认为是线程安全的。你可以在多线程中使用相同实例而不用先获取锁。

线程不安全的类

下面的类和函数通常是线程不安全的。在大多数情况下,只要一次只使用一个线程,你可以在任何线程中使用这些类。查看类文档了解更多细节。

注意:尽管NSSerializerNSArchiverNSCoderNSEnumerator 对象本身是线程安全的,它们列在这里是因为在使用它们时,改变它们封装的数据对象是不安全的。例如,在一个文档的情况下,改变已存档的对象图是不安全的。对于一个枚举,任何改变枚举集合的线程都是不安全的。

主线程中使用的类

下面的类必须在应用的主线程中使用。

  • NSAppleScript

可变VS不可变

不可变对象通常是线程安全的,一旦你创建它们,你可以在线程间安全的传递这些对象。当然,使用不可变对象时,你仍然需要记得正确使用引用计数器。如果不当释放一个没有保留的对象,你以后可能会引起异常。

可变对象通常是线程不安全的。在多线程应用中使用可变对象,引用必须使用锁来同步访问它们。(更多信息,参见原子操作(Atomic Operations))。一般来说,集合类与变化有关时(例如, NSMutableArrayNSMutableDictionary)是线程不安全的。也就是说,如果一个或多个线程正在改变相同的数组,问题可能会发生。你必须在读写的地方锁定以保证线程安全。

即使一个方法返回一个不可变对象,你永远不应该简单的假定返回的对象是不可变的。根据方法实现,返回的对象可能是可变或不可变的。例如,一个返回NSString类型的方法由于实现,实际返回的是一个 NSMutableString。如果你像保证对象是不可变的,你应该构造一个不可变的副本。

可重入

当操作“呼叫”同一个对象或不同对象的其他操作时,可重入是唯一的可能。保留和释放对象是这样的一个“呼叫”,有时会被忽视。

下表列出了部分明确可重入的基础框架。所有其他类可能也可能不是可重入的,或者他们在未来可重入。可重入的完整分析重来没做过,这个列表可能并不详尽。

类初始化

Objective-C运行时系统在类接收到其他消息前,发送一个initialize 消息给每个类对象。这样让类在被使用前,建立自己的运行环境。在多线程应用中,运行时保证只有一个线程,线程发送第一个消息给类执行initialize 方法。如果另一个线程试图发送消息给类,而这个类仍然在第一个线程的initialize 方法中,另一个线程会阻塞直到initialize 方法完成执行。与此同时,第一个线程可以继续调用类的其他方法。initialize 方法不应该依赖于另一个线程调用类的方法,如果是这样,两个线程都将成为死锁。

由于OS X 10.1.x以及早期版本的bug,在另一个线程执行完类的initialize方法前,一个线程可以发送消息到类。线程可以访问尚未完全初始化的值,可能导致应用崩溃。如果你遇到这个问题,在多线程前,你需要引入锁来防止访问该值直到它们初始化或强制类本身初始化。

自动释放池

每个线程维护自己NSAutoreleasePool 对象堆栈。Cocoa预计有个自动释放池在当前线程堆栈可用。如果释放池不可用,对象不能被释放,内存泄露。在基于应用工具包应用的主线程中,NSAutoreleasePool 对象自动创建和销毁,但次要线程(只有基础框架的应用)必须在使用Cocoa前创建自己创建。如果你的线程是长期的并潜在生成很多自动释放对象,你应该定期销毁并创建自动释放池(如应用工具包在主线程),否则,自动释放对象累积且内存占用增加。如果你的分离线程不使用Cocoa,你不需要创建自动释放池。

运行循环

每个线程有且只有一个运行循环。每次运行循环,因此每个线程都有自己的一组输入模式,这个模式决定当运行循环运行时监听哪些输入源。一个运行循环中定义的输入模式不影响另一个运行循环中定义的输入模式,即使它们肯那个具有相同的名称。

如果你的应用是基于应用工具包,主线程的运行循环自动运行,但次要线程(只有基础框架的应用)必须运行自己的运行循环。如果分离线程不进入运行循环,一旦分离方法执行完成,线程立即退出。

尽管有些外在表现,NSRunLoop类是线程不安全的。你必须在拥有它的线程调用这个类的实例方法。

应用工具包框架线程安全

以下章节描述应用工具包框架的通用线程安全。

线程不安全的类

下面的类和函数通常是线程不安全的。在大多数情况下,只要你一次只使用一个线程,你可以在任何线程中使用这些类。更多细节查阅类文档。

主线程中的类

以下类只能在应用的主线程中使用。

  • NSCell 以及其所有的后代
  • NSView 以及其所有的后代。更多信息,参见NSView限制( NSView Restrictions)。

窗口限制

你可以在次要线程中创建一个窗口。应用工具包确保与窗口相关的数据结构在主线程释放,来避免竞争条件。在处理大量窗口并发的应用中,窗口对象可能泄露。

你可以在次要线程中创建一个模态窗口。当主线程运行模态循环,应用工具包会阻塞调用次要线程。

事件处理限制

应用的主线程负责处理事件。NSApplicationrun 方法通常阻塞主线程,通常在应用的main函数中调用它。当应用工具包继续工作时,如果其他线程参与事件路径,操作顺序可能不对。例如,如果两个不同的线程响应关键事件,接收到的关键事件可能次序颠倒。通过让主线程处理事件,你会获得更加一致的用户体验。一旦接收到,如果需要,事件可以分发到次要线程来做进一步的处理。

你可以在次要线程调用NSApplication 的 postEvent:atStart:方法发布一个事件到主线程的事件队列。然而,用户输入事件不保证顺序。应用主线程依然负责处理事件队列中的事件。

绘制限制

当使用应用工具包的图形函数和类来绘制,应用工具包通常是线程安全的,包括NSBezierPathNSString 类。使用特定的类的详情将在以下章节中描述。关于绘制和可用的线程的额外信息,参见Cocoa绘制指南(CocoaDrawing Guide)。

NSView限制

NSView类通常是线程不安全的。你应该只在应用的主线程上创建、销毁、调整、移动和执行NSView 对象的其他操作。只要你将绘制调用与lockFocusIfCanDrawunlockFocus调用归在一起,在次要线程上绘制是线程安全的。

如果应用的次要线程希望使部分视图在主线程上重绘,它不能使用方法如setNeedsDisplay:,setNeedsDisplayInRect:setViewsNeedDisplay:。相反,它应该发送消息到主线程或者使用performSelectorOnMainThread:withObject:waitUntilDone: 方法来调用这些方法。

视图系统的图形声明(gstates)是per-thread。在单线程应用中,使用图形声明曾经是达到更好绘制性能的一个方法,但现在不是了。图形声明的不正确的使用会导致绘制代码比主线程绘图中效率更低。

NSGraphicsContext限制

NSGraphicsContext 类代表底层图形系统提供的绘制环境。每个NSGraphicsContext 实例拥有自己独立的绘图声明:坐标系统、裁剪、当前字体达到。主线程为每个NSWindow 实例自动创建类的实例。如果你在次要线程做任何绘制,会为该线程专门创建一个NSGraphicsContext实例。

如果你在次要线程做任何绘制,你必须手动的激活绘制调用。Cocoa不会自动更新来自次要线程的视图内容,所以你需要在完成绘制时调用NSGraphicsContext 的flushGraphics 方法。如果你的应用只绘制来自主线程的内容,你不需要激活绘制调用。

NSImage限制

一个线程可以创建一个NSImage 对象,得到图像缓冲区,并将其传递到主线程绘制。底层图像缓存在所有线程中共享。关于图片及缓存如何工作的更多信息,参见Cocoa绘制指南(CocoaDrawing Guide)。

核心数据框架

核心数据框架通常支持线程,虽然有些使用说明。更多说明信息,参见核心数据编程指南( Core Data Programming Guide)中的核心数据并发( Concurrency with Core Data)。

核心基础

核心基础完全是线程安全的,如果仔细的编程,你不会遇到任何有关竞争线程的问题。在一般情况下,它是线程安全的,例如当你查询、保留、释放并传递不可变对象。甚至从多个线程查询到的中央共享对象时线程安全的。

当Cocoa、核心基础的对象或内容突变时,它们是线程不安全的。例如,修改一个可变的数据或可变数组对象是线程不安全的,如你所料,修改一个不可变的数组中的对象同样是不安全的。其中一个原因是性能,性能在这种情况下是至关重要的。此外,在这个级别通常不可能达到绝对线程安全。你不能排除,例如,从一个集合中保留一个对象会造成不确定行为。集合本身可能在调用保留所包含的对象前就已经释放。

在这些情况下,核心基础对象从多个线程访问并突变,你的代码应该在访问点使用锁来防止同时访问。例如,枚举核心基础数组对象的代码必须在枚举block使用适当的锁调用,来防止别人改变数组。

 

官方原文地址:

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1http://www.apple.com/legal/policies/ideas.html -//apple_ref/doc/uid/_blank




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值