スレッドの安全性の要約
この付録では、OS X および iOS の主要なフレームワークの高水準スレッドの安全性について説明します。この付録の情報は変更される可能性があります。
Cocoa
複数のスレッドから Cocoa を使用する際のガイドラインは以下のとおりです。
- 不変オブジェクトは一般的にスレッドセーフです。それらを作成したら、これらのオブジェクトをスレッドとの間で安全に渡し渡されれることができます。一方、可変オブジェクトは一般にスレッドセーフではありません。スレッド型アプリケーションで可変オブジェクトを使用するには、アプリケーションを適切に同期させる必要があります。詳細については、可変に対する不変 を参照してください。
- "スレッドが安全でない" とみなされる多くのオブジェクトは、複数のスレッドから使用するのが安全でないだけです。これらのオブジェクトの多くは、一度に 1 つのスレッドである限り、どのスレッドからでも使用できます。アプリケーションのメインスレッドに特に制限されているオブジェクトは、そのまま呼び出されます。
- アプリケーションのメインスレッドは、イベントの処理を担当します。他のスレッドがイベントパスに関与している場合、Application Kit は引き続き動作しますが、操作が順不同で実行される恐れがあります。
- スレッドを使用してビューに描画したい場合は、NSView の lockFocusIfCanDraw メソッドと unlockFocus メソッドの間ですべての描画コードを囲みます。
- POSIX スレッドを Cocoa で使用するには、まず Cocoa をマルチスレッドモードにしなければなりません。詳細については、POSIX スレッドを Cocoa アプリで使用 を参照してください。
Foundation フレームワークのスレッドの安全性
Foundation フレームワークはスレッドセーフであり、Application Kit フレームワークはそうではないという誤解があります。残念ながら、これは全体的な一般化であり、やや誤解を招くものです。各フレームワークにはスレッドセーフな領域とスレッドセーフではない領域があります。以下のセクションでは、Foundation フレームワークの一般的なスレッドの安全性について説明します。
スレッドセーフなクラスと関数
以下のクラスおよび関数は、一般にスレッドセーフであると考えられています。最初にロックを取得することなく、複数のスレッドから同じインスタンスを使用できます。
-
NSArray
NSAssertionHandler
NSAttributedString
NSBundle
NSCalendar
NSCalendarDate
NSCharacterSet
NSConditionLock
NSConnection
NSData
NSDate
NSDateFormatter
NSDecimal 関数
NSDecimalNumber
NSDecimalNumberHandler
NSDeserializer
NSDictionary
NSDistantObject
NSDistributedLock
NSDistributedNotificationCenter
NSException
NSFileManager
NSFormatter
NSHost
NSJSONSerialization
NSLock
NSLog/NSLogv
NSMethodSignature
NSNotification
NSNotificationCenter
NSNumber
NSNumberFormatter
NSObject
NSOrderedSet
NSPortCoder
NSPortMessage
NSPortNameServer
NSProgress
NSProtocolChecker
NSProxy
NSRecursiveLock
NSSet
NSString
NSThread
NSTimer
NSTimeZone
NSUserDefaults
NSValue
NSXMLParser
オブジェクトの割り当てとカウント関数の保持
ゾーン関数とメモリ関数
スレッドセーフでないクラス
以下のクラスおよび関数は、一般にスレッドセーフではありません。ほとんどの場合、これらのクラスは、一度に 1 つのスレッドからのみ使用する限り、どのスレッドからでも使用できます。詳細については、クラスのドキュメントを参照してください。
-
NSArchiver
NSAutoreleasePool
NSCoder
NSCountedSet
NSEnumerator
NSFileHandle
NSHashTable 関数
NSInvocation
NSMapTable 関数
NSMutableArray
NSMutableAttributedString
NSMutableCharacterSet
NSMutableData
NSMutableDictionary
NSMutableOrderedSet
NSMutableSet
NSMutableString
NSNotificationQueue
NSPipe
NSPort
NSProcessInfo
NSRunLoop
NSScanner
NSSerializer
NSTask
NSUnarchiver
NSUndoManager
ユーザー名とホームディレクトリ関数
NSArchiver、NSCoder、 および NSEnumerator オブジェクトはそれら自身スレッドセーフですが、使用中にそれらによって包み込まれたデータオブジェクトを変更することは安全でないため、ここにリストされています。たとえば、アーカイバ (archiver) の場合、アーカイブされるオブジェクトグラフを変更することは安全ではありません。列挙子の場合、列挙されたコレクションを変更するスレッドは安全ではありません。
メインスレッドのみのクラス
次のクラスは、アプリケーションのメインスレッドからのみ使用しなければなりません。
NSAppleScript
可変に対する不変
不変オブジェクトは一般にスレッドセーフであり、一度作成すると、これらのオブジェクトをスレッド間で安全に渡し渡されることができます。もちろん、不変オブジェクトを使用する場合は、参照カウントを正しく使用することを忘れないでください。保持していないオブジェクトを不適切に開放すると、後で例外が発生する可能性があります。
可変オブジェクトは、一般にスレッドセーフではありません。スレッド化されたアプリケーションで可変オブジェクトを使用するには、アプリケーションはロックを使用してそれらにアクセスを同期しなければなりません。(詳細については、アトミック (微少) な操作 を参照してください)。一般的に、コレクションクラス (NSMutableArray、NSMutableDictionary など) は、突然変異が関係する場合にスレッドセーフではありません。つまり、1 つ以上のスレッドが同じ配列を変更している場合、問題が発生する可能性があります。スレッドの安全性を確保するために、読み取りと書き込みが発生する場所をロックしなければなりません。
メソッドが不変オブジェクトを返すと主張したとしても、返されたオブジェクトが不変であるとは決して考えるべきではありません。メソッドの実装に応じて、返されるオブジェクトは可変または不変である可能性があります。たとえば、 NSString の戻り値の型を持つメソッドは、その実装のために NSMutableString を実際には返します。あなたが持っているオブジェクトが不変であることを保証したいならば、あなたは不変のコピーを作るべきです。
リエントラント
リエントラントは、操作が同じオブジェクト内または別のオブジェクト内の他の操作を "呼び出す" 場合にのみ可能です。オブジェクトを保持したり解放したりすることは、時に見落とされるような "呼び出し" の 1 つです。
以下の表は、明示的にリエントラントな Foundation フレームワークの部分を示しています。他のすべてのクラスは、リエントラントでないかもしれないし、将来リエントラントになるかもしれない。リエントラントに関する完全な分析は一度も行われておらず、このリストは網羅的ではありません。
分散オブジェクト
NSConditionLockNSDistributedLock
NSLock
NSLog/NSLogv
NSNotificationCenter
NSRecursiveLock
NSRunLoop
NSUserDefaults
クラスの初期化
Objective-C 実行環境システムは、クラスが他の何らかのメッセージを受信する前に、すべてのクラスオブジェクトに initialize メッセージを送信します。これにより、実行環境を使用する前にクラスを設定する機会が与えられます。マルチスレッドアプリケーションでは、実行環境は、最初のメッセージをクラスに送信するスレッドである唯一のスレッドのみが initialize メソッドを実行することを保証します。最初のスレッドがまだ initialize メソッドにある間に 2 番目のスレッドがクラスにメッセージを送信しようとすると、2 番目のスレッドは initialize メソッドの実行が終了するまでブロックします。その間、最初のスレッドはクラスの他のメソッドを引き続き呼び出すことができます。initialize メソッドは、クラスのメソッドを呼び出す 2 番目のスレッドに依存するべきではありません。そうであれば、2 つのスレッドはデッドロック状態になります。
OS X バージョン 10.1.x 以前のバグのために、スレッドは、別のスレッドがそのクラスの initialize メソッドの実行を終了する前に、クラスにメッセージを送信する可能性があります。スレッドは、完全に初期化されていない値にアクセスし、アプリケーションをクラッシュさせる可能性があります。この問題が発生した場合は、ロックを導入して初期化されるまで値にアクセスしないようにするか、クラスを初期化する前にマルチスレッド化する必要があります。
自動解放プール
各スレッドは、独自の NSAutoreleasePool オブジェクトのスタックを保持します。Cocoa は、現在のスレッドのスタック上で常に自動解放プールが利用できると考えています。プールが利用できない場合、オブジェクトは解放されず、メモリがリークします。NSAutoreleasePool オブジェクトは、Application Kit に基づいたアプリケーションのメインスレッドで自動的に作成および破棄されますが、二次スレッド (および Foundation のみのアプリケーション) は、Cocoa を使用する前に独自のアプリケーションを作成しなければなりません。スレッドの寿命が長く、潜在的に多くの自動開放されたオブジェクトを生成している場合は、自動解放プールを定期的に破棄して作成する必要があります (メインスレッド上で Application Kit が行なうように)。それ以外の場合は、自動開放されたオブジェクトが蓄積され、メモリ使用量が増加します。分離したスレッドが Cocoa を使用していない場合は、自動解放プールを作成する必要はありません。
実行ループ
すべてのスレッドには 1 つの実行ループしかありません。ただし、各実行ループ、したがって各スレッドには、実行ループの実行時にどの入力ソースを聴取するかを決定する独自の入力モードのセットがあります。1 つの実行ループで定義された入力モードは、同じ名前を持っていたとしても、別の実行ループで定義されている入力モードには影響しません。
メインスレッドの実行ループは、アプリケーションが Application Kit に基づいている場合は自動的に実行しますが、二次スレッド (および Foundation のみのアプリケーション) は実行ループを自分自身で実行しなければなりません。切り離されたスレッドが実行ループに入らない場合、スレッドは、切り離されたメソッドの実行が終了するとすぐに終了します。
外見にもかかわらず、NSRunLoop クラスはスレッドセーフではありません。このクラスのインスタンスメソッドは、それを所有するスレッドからのみ呼び出す必要があります。
Application Kit フレームワークのスレッドの安全性
以下のセクションでは、Application Kit フレームワークの一般的なスレッドの安全性について説明します。
スレッドセーフでないクラス
以下のクラスおよび関数は、一般にスレッドセーフではありません。ほとんどの場合、これらのクラスは、一度に 1 つのスレッドからのみ使用する限り、どのスレッドからでも使用できます。詳細については、クラスのドキュメントを参照してください。
- NSGraphicsContext。詳細については、NSGraphicsContext の制限事項 を参照してください。
- NSImage。詳細については、NSImage の制限事項 を参照してください。
- NSResponder
- NSWindow とその子孫のすべて。詳細については、Window の制限事項 を参照してください。
メインスレッドだけのクラス
以下のクラスは、アプリケーションのメインスレッドからだけ使用しなければなりません。
- NSCell とその子孫すべて
- NSView とそのすべての子孫。詳細については、NSView の制限事項 を参照してください。
Window の制限事項
二次スレッド上で Window を作成できます。Application Kit は、競合状態を回避するために、window に関連したデータ構造がメインスレッド上で割り当て解除されることを保証します。多くの window を同時に処理するアプリケーションで window オブジェクトがリークする可能性があります。
二次スレッドでモーダル window を作成できます。Application Kit は、メインスレッドがモーダルループを実行している間、呼び出し側の二次スレッドをブロックします。
イベント処理の制限事項
アプリケーションのメインスレッドは、イベントの処理する責任があります。メインスレッドはN SApplication の run メソッドでブロックされたもので、通常はアプリケーションの main 関数で呼び出されます。他のスレッドがイベントパスに応答している場合、Application Kit は引き続き動作しますが、操作は順序どおりに行われません。たとえば、2 つの異なるスレッドがキーイベントに応答している場合、キーは順不同で受信できます。メインスレッドにイベントを処理させることで、より一貫したユーザーエクスペリエンスを実現できます。一度受信すると、イベントは必要に応じてさらに処理するために二次スレッドに急送できます。
二次スレッドから NSApplication の postEvent:atStart: メソッドを呼び出して、メインスレッドのイベントキューにイベントを送信できます。ただし、ユーザーの入力イベントに関しては順序は保証されません。アプリケーションのメインスレッドは、依然としてイベントキュー内のイベントを処理する責任があります。
描画の制限事項
Application Kit は、NSBezierPath および NSString クラスを含む、グラフィックスの関数およびクラスを使用して描画する場合、一般にスレッドセーフです。特定のクラスを使用するための詳細は、以下のセクションで説明します。描画とスレッドの追加情報については、Cocoa の描画ガイド を参照してください。
NSView の制限事項
NSView クラスは一般にスレッドセーフではありません。NSView オブジェクト上での作成、破棄、サイズ変更、移動、その他の操作は、アプリケーションのメインスレッドからのみ行なう必要があります。二次スレッドからの描画は、lockFocusIfCanDraw と unlockFocus への呼び出しで描画呼び出しを囲む限り、スレッドセーフです。
アプリケーションの二次スレッドがビューの一部をメインスレッド上に再描画したい場合、 display、 setNeedsDisplay:, setNeedsDisplayInRect:, あるいは setViewsNeedDisplay: のようなメソッドを使用してはいけません。代わりに、performSelectorOnMainThread:withObject:waitUntilDone: メソッドを使用してメインスレッドにメッセージを送信するか、それらのメソッドを呼び出す必要があります。
NSGraphicsContext の制限事項
NSGraphicsContext クラスは、基になるグラフィックス・システムによって提供される描画コンテキストを表します。各 NSGraphicsContext インスタンスは、独自の独立したグラフィック状態、つまり座標系、クリッピング、現在のフォントなどを保持します。クラスのインスタンスは、各 NSWindow インスタンスのメインスレッド上に自動的に作成されます。二次スレッドから何らかの描画を行なうと、NSGraphicsContext の新しいインスタンスがそのスレッド専用に作成されます。
二次スレッドから何らかの描画を行なう場合は、描画呼び出しを手動でフラッシュしなければなりません。Cocoa は二次スレッドから描画されたコンテンツではビューを自動的に更新しないため、描画を終了するときに NSGraphicsContext の flushGraphics メソッドを呼び出す必要があります。アプリケーションがメインスレッドのみからコンテンツを描画する場合、描画呼び出しをフラッシュする必要はありません。
NSImage の制限事項
一つのスレッドは NSImage オブジェクトを作成でき、イメージバッファに描画し、描画のためにそれをメインスレッドに渡す事ができます。基礎となるイメージキャッシュはすべてのスレッド間で共有されます。イメージとキャッシングの仕組みの詳細については、Cocoa の描画ガイド を参照してください。
Core Data フレームワーク
Core Datra フレームワークは一般にスレッド化をサポートしていますが、適用される使用上の警告がいくつかあります。これらの警告に関する情報は、Core data プログラミングガイド のCore Data との同時処理 を参照してください。
Core Foundation
Core Foundation は十分にスレッドセーフなため、注意深くプログラムすると、競合するスレッドに関連する何らかの問題に遭遇することはないでしょう。通常の場合、不変オブジェクトの照会、保持、解放、渡す事など、スレッドセーフです。複数のスレッドから照会される可能性のある中央共有オブジェクトであっても、確実にスレッドセーフです。
Core Foundation は、Cocoa と同様に、オブジェクトやその内容に対する変異に関してはスレッドセーフではありません。たとえば、可変データまたは可変配列オブジェクトを変更することは、スレッドセーフではありませんが、予想できるように、不変配列内のオブジェクトを変更することはありません。その理由の 1 つはパフォーマンスであり、これは、これらの状況では重要です。さらに、このレベルでは絶対的なスレッドの安全性を達成することは通常不可能です。たとえば、コレクションから取得したオブジェクトを保持することによる不確定な動作などを除外することはできません。格納されたオブジェクトを保持するための呼び出しが行われる前に、コレクション自体が解放される可能性があります。
Core Foundation オブジェクトを複数のスレッドからアクセスして変異する場合は、アクセスポイントでロックを使用することにより、コードを同時アクセスから保護する必要があります。たとえば、Core Foundation 配列のオブジェクトを列挙するコードでは、列挙するブロックを囲む適切なロック呼び出しを使用して、他の誰かが配列を変異させるのを防ぐ必要があります。