スレッドプログラミングについて
長年にわたって、コンピュータの最大性能は、コンピュータの中心にある単一のマイクロプロセッサの速度によって大きく制限されていました。しかし、個々のプロセッサの速度が実用限界に達するにつれ、チップメーカーはマルチコア設計に切り替え、複数のタスクを同時に実行する機会をコンピュータに与えました。また、OS X はシステム関連のタスクを実行できるときはいつでも、これらのコアを利用することができますが、独自のアプリケーションでスレッドを利用することもできます。
スレッドとは何か?
スレッドは、アプリケーション内で複数の実行パスを実装する比較的軽い方法です。システムレベルでは、プログラムは、ニーズと他のプログラムのニーズに基づいて、各プログラムに実行時間を与えるシステムを平行して実行します。しかし、各プログラムの中には、1 つ以上の実行スレッドが存在し、異なるタスクを同時に実行するか、ほぼ同時に実行することができます。実際には、システム自体がこれらの実行スレッドを管理し、使用可能なコア上で実行し、必要に応じて他のスレッドの実行を優先して割り込むようにスケジュールに組み入れます。
技術的な観点では、スレッドは、コードの実行を管理するために必要なカーネルレベルとアプリケーションレベルのデータ構造の組み合わせです。カーネルレベルの構造は、スレッドへのイベントの急送と、利用可能なコアの 1 つに対するスレッドの優先的スケジュール化を調整します。アプリケーションレベルの構造には、関数呼び出しを格納するための呼び出しスタック、およびアプリケーションがスレッドの属性と状態を管理および操作するために必要な構造が含まれます。
非並行アプリケーションでは、実行スレッドは 1 つだけです。そのスレッドは、アプリケーションの main ルーチンで開始および終了し、アプリケーションの全体的な動作を実装するために、異なるメソッドまたは関数に 1 つずつ分岐します。対照的に、同時実行性をサポートするアプリケーションは、1 つのスレッドで始まり、追加の実行パスを作成するために必要に応じてたくさん追加します。新しい各パスには、アプリケーションの main ルーチン内のコードとは独立して実行する独自のカスタムスタートルーチンがあります。アプリケーション内に複数のスレッドを持つことは、2 つの非常に重要な潜在的な利点を提供します。
- 複数のスレッドは、アプリケーションの関知できる応答性を向上させることができます。
- 複数のスレッドは、マルチコアシステム上でアプリケーションのリアルタイムパフォーマンスを向上させることができます。
アプリケーションにスレッドが 1 つしかない場合、その 1 つのスレッドはすべてを実行する必要があります。イベントに応答し、アプリケーション・ウィンドウを更新し、アプリケーションの動作を実装するために必要なすべての計算を実行する必要があります。スレッドを 1 つしか持たないという問題は、一度に 1 つしか処理できないということです。あなたの計算の 1 つが完了するのに長い時間がかかるとどうなるでしょうか?コードが必要な値を計算中に忙しい間、アプリケーションはユーザーイベントへの応答とウィンドウの更新を停止します。この動作が十分に長く続く場合、ユーザーはアプリケーションがハングアップしていると考え、強制的に終了させようとするかもしれません。ただし、カスタム計算を別のスレッドに移動した場合、アプリケーションのメインスレッドは、より適切なタイミングでユーザーのやりとりに自由に応答できます。
最近のマルチコアコンピュータでは、スレッドによって、一部の種類のアプリケーションでパフォーマンスが向上する方法が提供されています。異なるタスクを実行するスレッドは、異なるプロセッサコアで同時に実行できるので、アプリケーションが一定時間内に実行する作業量を増やすことができます。
もちろん、スレッドは、アプリケーションのパフォーマンス上の問題を修正するための万能薬ではありません。スレッドによって提供される利点に加えて、潜在的な問題が発生します。アプリケーションで複数の実行パスを使用すると、コードにかなりの多さの複雑さが加わる可能性があります。各スレッドは、アプリケーションの状態の情報を破壊しないように、他のスレッドとそのアクションを調整する必要があります。単一のアプリケーション内のスレッドは同じメモリ空間を共有するため、同じデータ構造のすべてにアクセスできます。2 つのスレッドが同じデータ構造を同時に操作しようとすると、あるスレッドが結果であるデータ構造を破壊しようとしているのに、別の変更を別のスレッドが上書きする可能性があります。適切な保護がなされていても、コードに微妙な (そして微妙ではない) バグを導入するコンパイラの最適化に注意する必要があります。
スレッドの用語集
スレッドとそれらのサポート技術について議論するには、いくつかの基本的な用語を定義する必要があります。
UNIX システムに精通している場合は、"タスク" という用語が本書では違って使用されていることがあります。UNIX システムでは、"タスク" という用語は、実行中のプロセスを参照するために使用されます。
本書では、以下の用語を使用しています。
- スレッド という用語は、コードの別の実行パスを指すために使用されます。
- プロセス という用語は、実行可能ファイルを参照するために使用され、複数のスレッドを含むことができます。
- タスク という用語は、実行する必要がある作業の抽象的な概念を指すために使用されます。
スレッドの代替
スレッドを自分自身で作成する際の 1 つの問題は、コードに不確実性が加わることです。スレッドは、アプリケーションの並行性をサポートするための比較的低レベルで複雑な方法です。デザインの選択肢を十分に理解できない場合は、同期やタイミングの問題が簡単に発生する可能性があり、その重要度は、微妙な動作上の変化からアプリケーションのクラッシュ、ユーザーのデータの破損までさまざまです。
考慮すべきもう 1 つの要素は、スレッドまたは同時実行が必要かどうかです。スレッドは、同じプロセス内で同時に複数のコードパスを実行する方法の特定の問題を解決します。ただし、実行している作業の量が並行性を保証していない場合があります。スレッドは、メモリ消費と CPU 時間の両方で、プロセスに膨大なオーバーヘッドをもたらします。このオーバーヘッドが目的のタスクにはあまりにも大きいか、他のオプションを実装するのが簡単であることがわかるかもしれません。
表 1-1 に、スレッドの代替方法の一部を示します。この表には、スレッド (操作オブジェクトや GCD など) の代替テクノロジと既存の単一スレッドを効率的に使用するための代替技術の両方が含まれています。
表 1-1 スレッドへの代替技術
技術 | 説明 |
操作オブジェクト | OS X v10.5 で導入された。操作オブジェクトは、通常はセカンダリスレッドで実行されるタスクのラッパーです。このラッパーは、タスクを実行するスレッド管理の側面を隠し、タスク自体に集中するのを自由にさせます。通常は、これらのオブジェクトを操作キューオブジェクトとともに使用し、このオブジェクトは、実際には 1 つ以上のスレッド上の操作オブジェクトの実行を管理します。 操作オブジェクトの使用方法の詳細については、並行処理プログラミングガイド を参照してください。 |
グランドセントラルディスパッチ (GCD) | Mac OS X v10.6 で導入された Grand Central Dispatch は、スレッド管理ではなく実行する必要のあるタスクに集中できるようにする、スレッドの別の代替手段です。GCD を使用して、実行したいタスクを定義し、適切なスレッド上でタスクのスケジューリングを処理する作業キューにタスクを追加します。作業キューは、スレッドを使用して自分自身で行うよりも効率的にタスクを実行するために、使用可能なコアの数と現在の負荷を考慮します。 GCDと作業キューを使用する方法については、並行処理プログラミングガイド を参照してください。 |
アイドル時間通知 | 比較的短い優先度と非常に低い優先度のタスクの場合、アイドル時間通知を使用すると、アプリケーションがビジー状態でないときにタスクを一度に実行できます。Cocoa は、NSNotificationQueue オブジェクトを使用してアイドル時間通知をサポートしています。アイドル時間通知を要求するには、NSPostWhenIdle オプションを使用してデフォルトの NSNotificationQueue オブジェクトに通知を送信します。実行ループがアイドルになるまで、キューは通知オブジェクトの配信を遅延させます。詳細については、通知プログラミングトピック を参照してください。 |
非同期関数 | システムインタフェースには、自動的な並行処理を提供する多くの非同期関数が含まれています。これらの API は、システムデーモンとプロセスを使用したり、カスタムスレッドを作成してタスクを実行したり、結果を返したりします。(実際の実装は、コードから分離されているため関係がありません。) アプリケーションを設計するときに、非同期動作を提供する関数を探し、カスタムスレッド上で同等の同期関数を使用する代わりにそれらを使用することを検討してください。 |
タイマー | アプリケーションのメインスレッド上のタイマーを使用して定期的なタスクを実行できますが、スレッドを必要とするのはあまりにも些細です。しかし、定期的な間隔でサービスを依頼する必要があります。タイマーの詳細については、タイマーソース を参照してください。 |
別々のプロセス | スレッドよりも重いが、別のプロセスを作成すると、タスクがアプリケーションに接しているだけの場合に役立ちます。タスクに大量のメモリーが必要な場合や、root 特権を使用して実行しなければならない場合は、プロセスを使用することができます。たとえば、32 ビットアプリケーションが結果をユーザーに表示しているときに、64 ビットサーバープロセスを使用して大きなデータセットを計算することができます。 |
スレッドのサポート
スレッドを使用する既存のコードを使用している場合、OS X と iOS はアプリケーション内でスレッドを作成するためのいくつかの技術を提供します。さらに、両方のシステムでは、これらのスレッドで実行する必要がある作業の管理と同期もサポートしています。以下のセクションでは、OS X と iOS でスレッドを操作する際に注意する必要がある、主要なテクノロジーについて説明します。
スレッドパッケージ
基本的なスレッドの実装メカニズムは Mach スレッドですが、Mach レベルでスレッドを扱うことは (もしあっても) めったにありません。代わりに、通常はより便利な POSIX API またはその派生物の 1 つを使用します。ただし、Mach の実装では、すべてのスレッドの基本機能が提供されており、優先的実行モデルと、スレッドが互いに独立しているようにスケジュールする機能が含まれています。
リスト 2-2 に、アプリケーションで使用できるスレッドの技術を示します。
表 1-2 スレッドの技術
技術 | 説明 |
Cocoa スレッド | Cocoa は、NSThread クラスを使用してスレッドを実装します。Cocoa は NSObject 上のメソッドも提供して、新しいスレッドを生成し、すでに実行中のスレッドでコードを実行します。詳細については、NSThread の使用 とおよび NSObject を使ってスレッドを生成 を参照してください。 |
POSIX スレッド | POSIX スレッドは、スレッドを作成するための C を基本としたインタフェースを提供します。Cocoa アプリケーションを作成していない場合、これはスレッドを作成するための最良の選択です。POSIX インターフェイスは比較的使いやすく、スレッドの構成に柔軟性があります。詳細については、POSTIX スレッドの使用 を参照してください。 |
多重処理サービス | マルチプロセシング (多重処理) サービスは、古いバージョンの Mac OS から移行するアプリケーションで使用される遺産と化した C を基本としたインターフェイスです。この技術は OS X でのみ利用可能であり、新たな開発を避けるべきです。代わりに、NSThread クラスまたは POSIX スレッドを使用する必要があります。この技術に関するさらに詳しい情報が必要な場合は、マルチプロセシングサービスプログラミングガイド を参照してください。 |
アプリケーションレベルでは、すべてのスレッドは基本的に他のプラットフォームと同じように動作します。スレッドを開始すると、スレッドは実行中、準備中、ブロック中の 3 つの主な状態のいずれかで実行されます。スレッドが現在実行されていない場合、スレッドはブロックされ、入力を待っているか、実行準備が整っているが、まだスケジュールされていません。スレッドは、最後に終了して終了状態になるまで、これらの状態の間を行き来し続けます。
新しいスレッドを作成するときは、そのスレッドのエントリポイント関数 (または Cocoa スレッドの場合はエントリポイントメソッド) を指定しなければなりません。このエントリポイント関数は、スレッド上で実行したいコードを構成します。関数が戻るか、またはスレッドを明示的に終了すると、スレッドは永久に停止し、システムによって再開されます。スレッドはメモリと時間の点で比較的高価なため、エントリポイント関数がかなりの作業を行うか、実行ループを設定して定期的な作業を実行することをお勧めします。
使用可能なスレッドテクノロジーとその使用方法の詳細については、スレッド管理 を参照してください。
実行ループ
実行ループは、スレッド上で非同期に到着するイベントを管理するために使用されるインフラストラクチャです。実行ループは、スレッドの 1 つ以上のイベントソースを監視することによって機能します。イベントが到着すると、システムはスレッドを起動し、イベントを実行ループに急送し、実行ループはイベントを指定したハンドラに急送します。イベントが存在せず、処理する準備ができていなけれれば、実行ループはスレッドをスリープ状態にします。
作成したスレッドで実行ループを使用する必要は全くありませんが、そうすることでユーザにとってより良い経験値を提供できます。実行ループを使用すると、最小限のリソースを使用する長寿命のスレッドを作成できます。実行ループは何もする事がないとスレッドをスリープ状態にするので、ポーリングの必要性がなくなるため、CPU サイクルが浪費され、プロセッサ自体がスリープするのを防ぎ電力を節約できなくなります。
実行ループを構成するためにあなたがなすべき事は、スレッドを起動し、実行ループオブジェクトへの参照を取得し、イベントハンドラをインストールし、実行ループを実行するよう指示するだけです。OS X が提供するインフラストラクチャは、メインスレッドの実行ループの構成を自動的に処理します。ただし、永続的なセカンダリスレッドを作成する場合は、それらのスレッドの実行ループを自分で構成する必要があります。
実行ループの詳細と使用方法の例は、実行ループ で提供します。
同期ツール
スレッドプログラミングの危険の 1 つは、複数スレッド間のリソース競合です。複数のスレッドが同じリソースを同時に使用または変更しようとすると、問題が発生する可能性があります。問題を軽減する 1 つの方法は、共用リソースを完全に排除し、各スレッドが動作する独自の個別のリソース・セットを持つようにすることです。ただし、完全に別のリソースを維持することはできないので、ロック、条件、微少操作、およびその他の手法を使用して、リソースへのアクセスを同期させる必要があります。
ロック (Lock) は、一度に 1 つのスレッドだけが実行できるコードに対する強力な保護形式を提供します。最も一般的な型のロックは mutex としても知られる相互排除ロックです。スレッドが現在別のスレッドによって保持されている mutex を取得しようとすると、他のスレッドによってロックが解除されるまでブロックされます。いくつかのシステムフレームワークは、mutex ロックをサポートしていますが、それらはすべて同じ基礎技術に基づいています。さらに、Cocoa は、再帰などのさまざまな型の動作をサポートするために、mutex ロックのいくつかのバリエーションを提供しています。利用可能なロックの型の詳細については、ロック(Locks) を参照してください。
ロックに加えて、システムはアプリケーション内のタスクの適切な順序付けを保証する条件をサポートしています。条件は門番として機能し、指定されたスレッドが表す条件が真になるまでブロックします。その状態が発生すると、条件によってスレッドが解放され、スレッドが続行されます。POSIX レイヤーと Foundation フレームワークの両方が条件を直接サポートしています。(演算オブジェクトを使用する場合は、演算オブジェクト間の依存関係を構成して、タスクの実行を順序付けることができ、これは、条件によって提供される動作と非常によく似ています。)
ロックと条件は並行処理の設計では非常に一般的ですが、微少操作はデータへのアクセスを保護して同期する別の方法です。微少 (atomic) 操作は、スカラーデータ型に対して数学的または論理的な演算を実行できる状況では、ロックに対する軽量の代替方法を提供します。微少操作では、特殊なハードウェア命令を使用して、他のスレッドがアクセスする前に変数の変更が完了するようにします。
使用可能な同期ツールの詳細については、同期ツール を参照してください。
スレッド間通信
良い設計は必要な通信量を最小限に抑えますが、ある時点でスレッド間の通信が必要になります。(スレッドの仕事はあなたのアプリケーションの仕事をすることですが、もしその仕事の結果が決して使われないのであれば、どう言う事ですか?) スレッドは新しい仕事の要求を処理したり、あなたのアプリケーションのメインスレッドに進行状況を報告する必要があります。このような状況では、あるスレッドから別のスレッドに情報を取得する方法が必要です。幸運なことに、スレッドが同じプロセス空間を共有するということは、通信の選択肢がたくさんあることを意味します。
スレッド間で通信するには多くの方法があり、それぞれ独自のメリットとデメリットがあります。スレッドとローカル間の保管の設定は、OS X で使用できる最も一般的な通信メカニズムをリストしています (メッセージキューと Cocoa 分散オブジェクトを除いて。これらの技術は iOS でも利用可能です。) この表の技術は、複雑さの増加する順序にリストされています。
表 1-3 通信メカニズム
メカニズム | 説明 |
ダイレクトメッセージ | Cocoa アプリケーションは、セレクタを他のスレッドで直接実行する機能をサポートしています。この機能は、あるスレッドが本質的に他のスレッド上でメソッドを実行できることを意味します。これらはターゲットスレッドのコンテキストで実行されるため、この方法で送信されたメッセージは自動的にそのスレッドでシリアル化されます。入力ソースについては、セレクターソースを Cocoa で実行 を参照してください。 |
グローバル変数、共有メモリ、およびオブジェクト | 2 つのスレッド間で情報を通信するもう 1 つの簡単な方法は、グローバル変数、共有オブジェクト、または共有メモリブロックを使用することです。共有変数は高速かつ単純ですが、ダイレクトメッセージよりも脆弱です。共有変数は、コードの正確性を保証するために、ロックやその他の同期メカニズムで慎重に保護しなければなりません。そうしないと、競合状態、データの破損、またはクラッシュにつながる可能性があります。 |
条件 | 条件は、スレッドがコードの特定の部分をいつ実行するかを制御するために使用できる同期ツールです。あなたは条件を門番と考えることができ、指定された条件が満たされたときにのみスレッドを実行させることができます。条件の使用方法については、条件の使用 を参照してください。 |
実行ループソース | カスタム実行ループソースは、スレッド上でアプリケーション特有のメッセージを受信するように設定するものです。イベント駆動型であるため、実行ループソースは、スレッドが何もする事がなければ自動的にスリープ状態になり、スレッドの効率が向上します。実行ループと実行ループソースの詳細については、実行ループ を参照してください。 |
ポートとソケット | ポートに基づいた通信は、2 つのスレッド間で通信するためのより手の込んだ方法ですが、非常に信頼性の高いテクニックです。さらに重要なのは、ポートとソケットを使用して、他のプロセスやサービスなどの外部エンティティと通信できることです。効率のために、実行ループソースを使用してポートが実装されるため、ポート上で待機中のデータがない場合スレッドはスリープします。実行ループとポートに基づいた入力ソースについては、実行ループ を参照してください。 |
メッセージキュー | 従来のマルチプロセッシングサービスでは、着信データおよび送信データを管理するための先入れ先出し (FIFO) キュー抽象化が定義されています。メッセージキューは単純で便利ですが、他の通信技術ほど効率的ではありません。メッセージキューの使用方法の詳細については、マルチプロセッシングサービスプログラミングガイド を参照してください。 |
ココア分散オブジェクト | 分散オブジェクトは、ポートに基づいた通信の高水準な実装を提供する Cocoa のテクノロジーです。スレッド間通信にこのテクノロジーを使用することは可能ですが、大量のオーバーヘッドが発生するため、スレッド間通信にはこのテクノロジーを使用することはできません。分散オブジェクトは、プロセス間のオーバーヘッドがすでに高い他のプロセスとの通信に、はるかに適しています。詳細については、分散オブジェクトプログラミングのトピックス を参照してください。 |
デザインのヒント
以下のセクションでは、コードの正確性を保証する方法でスレッドを実装するのに役立つガイドラインを示します。これらのガイドラインの中には、独自のスレッドコードでより良いパフォーマンスを達成するためのヒントもあります。パフォーマンスに関するヒントと同様に、コードを変更する前、実行中、実行後に関連するパフォーマンス統計を常に収集する必要があります。
スレッドを明示的に作成しない
スレッド作成コードを手動で記述するのは面倒で、エラーが起こりやすい可能性があり、可能であればそれを避けるべきです。OS X と iOS は、他の API を使用して並行処理を暗黙的にサポートしています。自分でスレッドを作成するのではなく、非同期的 API、GCD、または操作オブジェクトを使用して作業を行うことを検討してください。これらのテクノロジーは、舞台裏でスレッド関連の作業を行い、正しく行うことが保証されています。さらに、GCD や操作オブジェクトなどのテクノロジーは、現在のシステム負荷に基づいてアクティブなスレッドの数を調整することで、自分のコードよりも効率的にスレッドを管理するように設計されています。GCD と操作オブジェクトの詳細については、並行処理プログラミングガイド を参照してください。
スレッドを合理的にビジー状態に保つ
スレッドを手動で作成および管理する事を決定した場合は、スレッドが貴重なシステムリソースを消費することに注意してください。スレッドに割り当てるタスクがかなり長寿命で生産性が高いことを確認するために最善を尽くしてください。同時に、ほとんどの時間をアイドル状態にしているスレッドを終了させるのを恐れてはいけません。スレッドは重要な量のメモリを使用しますが、その一部は有線で接続されているため、アイドル状態のスレッドを解放するだけでアプリケーションのメモリの足跡が削減されるだけでなく、他のシステムプロセスが使用する物理メモリをより多く解放します。
共有データ構造を避ける
スレッドに関連したリソースの競合を回避する最も単純で簡単な方法は、プログラム内の各スレッドに、必要なデータのそれ自身のコピーをすることです。パラレルコードは、スレッド間の通信およびリソースの競合を最小限に抑える場合に最適です。
マルチスレッドアプリケーションの作成は難しいです。たとえあなたが非常に注意深く、あなたのコード内のすべての適切な場所で共有データ構造をロックしても、コードは安全な意味を持たないかもしれません。たとえば、共有データ構造が特定の順序で変更されることが予想される場合、コードに問題が発生する可能性があります。これを補うためにコードを交流を基礎にしたモデルに変更すると、その後に複数のスレッドを持つことのパフォーマンス上の利点が無効になる可能性があります。最初にリソースの競合を解消すると、優れたパフォーマンスでよりシンプルなデザインが得られることがよくあります。
スレッドとユーザーインターフェイス
アプリケーションにグラフィカルユーザーインターフェイスがある場合は、ユーザー関連のイベントを受け取り、アプリケーションのメインスレッドからインターフェイスの更新を開始することをお勧めします。このアプローチは、ユーザーイベントの処理とウィンドウコンテンツの描画に関連する同期の問題を回避するのに役立ちます。Cocoa のようないくつかのフレームワークでは、一般的にこの動作が必要ですが、そうでないものであっても、メインスレッド上でこの動作を維持することは、ユーザインタフェースを管理するロジックを単純化するという利点があります。
他のスレッドからグラフィカル操作を実行することが有利ないくつかの注目すべき例外があります。たとえば、セカンダリスレッドを使用してイメージを作成して処理し、その他のイメージ関連の計算を実行できます。これらの操作にセカンダリスレッドを使用すると、パフォーマンスが大幅に向上します。しかし、特定のグラフィカル操作について確信が持てない場合は、メインスレッドから実行するように計画してください。
Cocoa スレッドの安全性の詳細については、スレッドの安全性の要約 を参照してください。Cocoa での描画の詳細については、Cocoa 描画ガイド を参照してください。
終了時のスレッド動作に注意
プロセスは、切り離されていないスレッドがすべて終了するまで実行します。デフォルトでは、アプリケーションのメインスレッドのみが切り離されず作成されますが、同様に他のスレッドをそのように作成することもできます。ユーザーがアプリケーションを終了すると、通常、切り離されたスレッドによって行われた作業はオプションとみなされるため、すべての切り離されたスレッドを直ちに終了させるのが適切な動作とみなされます。ただし、アプリケーションがバックグラウンドスレッドを使用してデータをディスクに保存したり、他の重要な作業を行っている場合は、アプリケーションが終了したときにデータが失われないように、スレッドを切り離されないものとして作成できます。
切り離されない型 (結合可能とも呼ばれます) としてスレッドを作成するには、あなたの作業に余分な作業が必要です。ほとんどのハイレベル・スレッド・テクノロジーはデフォルトで結合可能なスレッドを作成しないため、スレッドを作成するために POSIX API を使用する必要があります。さらに、アプリケーションのメインスレッドにコードを追加して、切り離されないスレッドを最後に終了したときにスレッドに参加させなければなりません。結合可能なスレッドの作成については、スレッドの切り離された状態の設定 を参照してください。
Cocoa アプリケーションを書いている場合は、applicationShouldTerminate: デリゲートメソッドを使用して、ある時までアプリケーションの終了を遅らせるか、まったくキャンセルすることもできます。終了を遅らせる場合、重要なスレッドがタスクを完了するまで待ってから、 replyToApplicationShouldTerminate: メソッドを呼び出す必要があります。これらのメソッドの詳細については、NSApplication クラスリファレンス を参照してください。
例外処理
例外処理メカニズムは、現在の呼び出しスタックに依存し、例外が throw されたときに必要なクリーンアップを実行します。各スレッドには独自の呼び出しスタックがあるため、各スレッドは独自の例外をキャッチする責任があります。セカンダリスレッド内で例外をキャッチできない場合は、メインスレッドで例外をキャッチできない場合と同じで、所有プロセスは終了します。捕捉されない例外を別のスレッドに throw して処理することはできません。
現在のスレッド内の例外的な状況を別のスレッド (メインスレッドなど) に通知する必要がある場合は、例外をキャッチして、何が起こったかを示すメッセージを別のスレッドに送信する必要があります。モデルや実行しようとしているものによっては、例外を捕捉したスレッドは処理を続行したり (もし可能であれば)、指示を待ったり、単に終了することができます。
場合によっては、例外ハンドラが自動的に作成されることがあります。たとえば、Objective-C の @synchronized ディレクティブには、暗黙的な例外ハンドラを含んでいます。
スレッドをきれいに終了
スレッドが終了する最善の方法は、それがメイン・エントリポイント・ルーチンの最後に到達するようにすることです。直ちにスレッドを終了させる関数がありますが、これらの関数は最後の手段としてのみ使用するべきです。スレッドが自然な終点に到達する前にスレッドを終了させると、そのスレッドはクリーンアップされません。スレッドがメモリを割り当てていたり、ファイルを開いたり、他の型のリソースを取得したりすると、コードがそれらのリソースを再利用できなくなり、メモリリークやその他の潜在的な問題が発生する可能性があります。
スレッドを終了する適切な方法の詳細については、スレッドの終了 を参照してください。
ライブラリでのスレッドの安全性
アプリケーション開発者は、アプリケーションが複数のスレッドで実行するかどうかを制御できますが、ライブラリ開発者はしません。ライブラリを開発する場合は、呼び出し元のアプリケーションがマルチスレッドであると仮定しなければならないか、いつでもマルチスレッドに切り替えることができます。結果として、コードの重要な部分には常にロックを使用する必要があります。
ライブラリ開発者にとっては、アプリケーションがマルチスレッド化された場合にのみロックを作成することは賢明ではありません。ある時点でコードをロックする必要がある場合は、ライブラリの初期段階でロックオブジェクトを作成し、ライブラリを初期化するための明示的な呼び出しが必要です。このようなロックを作成するために静的ライブラリの初期化関数を使用することもできますが、それ以外の方法がない場合にのみ、そのようにしてください。初期化関数を実行すると、ライブラリをロードするのに必要な時間が増え、パフォーマンスに悪影響を及ぼす可能性があります。
Cocoa ライブラリを開発している場合、アプリケーションがマルチスレッドになったときに通知を受けたい時は、NSWillBecomeMultiThreadedNotification の監視者として登録できます。ただし、ライブラリコードが呼び出される前に急送される可能性があるため、この通知を受け取ることに頼るべきではありません。