アプリケーションを開発していると、時間がかかる処理を別スレッドとして実装したい場合があります。例えば、データベースの検索、ファイルの大量コピー、プリンアウト、ネットワーク経由のダウンロードなどがそうです。こうした処理を「普通」に実装すると、その間ユーザは何も操作できず処理が終了するまで待たなければいけなくなります。Mac OS X環境では、そうした処理が発生して1秒が経過するとレインボーカーソルが回り出します。まあ、Mac OS 9とは違い、別アプリケーションに切り替えて作業は続けられますので、以前よりはマシなわけですが...やはり、美しくありません。
Carbonアプリケーションにスレッディングを実装する方法は2種類あります。ひとつは、Multiprocessing ServicesのプリエンプティブなMPタスク、もうひとつは、Thread ManagerによるCooperative(協調的)なスレッドを使う方法です。MPタスクの方がよりCPU効率の良い並列処理を提供しますが、Thread-Safe API(スレッド内で安全に利用できるAPI)が、Core Foundation、File Manager、Open Transportなどごく一部に限られているため、利用目的が限定されてしまいます。つまり、MPタスクのスレッド内ではQuickDrawもQuickTimeも使えないわけです。(そう言えばQuartz 2DやOpenGLはどうなのかしら?)よって現状では、マルチCPUで大量の演算処理を効率良く実行する場合などに利用されるのが一般的です。
それと比較して、Thread Managerが提供するスレッド内では、ほとんどのCarbon APIが利用可能です。Mac OS 9時代には、スレッディングと名前は付いていましたが、OS自体のプロセススケジューリングが貧弱だったために、その真価は発揮されていませんでした。ユーザがメニューをクリックしたりウィンドウの移動やリサイズをする度に、スレッド内の処理が止まり、とてもじゃないですが並列処理と呼べるような代物ではなかったわけです。しかし、Mac OS X環境でのスレッディングはPOSIXのpthreadの上に乗っかっており、CPUの高速化やCarbonイベントモデルの導入に伴い、十分に使える仕組みへと変貌しています。当然、メニュークリックやウィンドウの移動で処理がストップすることもありませんし、マルチCPUならば、それなりに両CPUをうまく使いわけてくれます。
では、さっそくスレッディング処理を実装してみましょう。まずは、アプリケーションにCarbon Event Timerルーチンをインストールし、そこでYieldToAnyThread()を呼ぶようにします。YieldToAnyThread()は各スレッドを切り替える役目を担っています。実行判断に利用している外部変数のm_threadには、現在起動しているスレッド数が代入されています(ゼロなら起動中のスレッドは存在しない)。Timerルーチンが呼ばれる間隔は、スレッディングへの時間配分に一致します(ここでは0.01秒毎。)

続いて、main.nibと名前を付けたNibファイルに「ThreadWindow」というウィンドウオブジェクトを用意します。そこには、id=1、signature='Demo'、command='not!'のキャンセルボタンと、id=2、signature='Demo'のEdit Textコントロールを配置します。
メニュー選択などで次に紹介するdisplayThreadWindow()を実行させると、ウィンドウをオープンし、NewThread()で新しいスレッドを作成し起動させます。スレッドの処理ルーチンとしてmyThread()が、受け渡しデータとしてこのウィンドウのWindowRefが登録されます。この時、m_threadの値を1増加させ、新規のスレッドが加わったことをTimerルーチンに知らせます。また、スレッドの終了判断には、SetWRefCon()で設定できるウィンドウのリファレンス値を使います。この値が1以上ならばスレッディング継続、ゼロならば終了を意味します。

次のmyThread()が実際の処理ルーチンです。各スレッドの処理状況を見るために、ウィンドウのリファレンス値を増加させ、それをEdit Text Controlに表示させます。ちょうどカウンターが増加していく感じですね。ここでも定期的にYieldToAnyThread()を呼び、別スレッドに処理を切り替えることを忘れないでください。GetWRefCon()で得た値がゼロであれば、その時点でウィンドウを閉じて処理から抜け出ます。

最後は、このウィンドウのCarbon Event Handlerルーチンです。ウィンドウのCloseボタンと、配置された「キャンセル」ボタンのクリックに反応し、SetWRefCon()にゼロを代入します。この指示を受けたmyThread()ルーチンは、ウィンドウを閉じてスレッディングから抜け出ます。

スレッド処理に多くの時間を配分したい場合には、より凝ったYieldToAnyThread()の呼び出し方もあるようですが、今回は一番簡単な手法を紹介してみました。凝った方法を試してみたい方は、Apple社のSample Codeサイトに登録されている「GrabBag」というサンプルを参照してください。
http://developer.apple.com/samplecode/Sample_Code/Human_Interface_Toolbox/Mac_OS_High_Level_Toolbox/GrabBag.htm
ただし、このサンプルで使われているのは、Mac OS Xが10.0や10.1だったころの手法のようで、現状の10.2でもこれが最良なのかどうかは未確認です。今回使用したスレッディングAPIは、すべてThread Managerに属しています。また、それらが定義されているUniversal Interfacesは、Threads.hです。Thread Managerに関するドキュメントは、以下のCarbon Documentサイトから参照できます。
http://developer.apple.com/techpubs/macosx/Carbon/oss/ThreadManager/threadmanager.html
ただし、ここにはPDFドキュメントが登録されていません。そちらが必要な場合には、次のURLに別途ありますのでダウンロードしてみてください。
http://developer.apple.com/techpubs/macos8/pdf/pdf.html
また、Mac OS X環境の様々なスレッディング(UNIX,Carbon,Cocoa)について知りたい方は、まず最初にTechnical Noteの2028番「Threading Architectures」を参考にされると良いでしょう。嬉しいことに、このTechnical Noteは日本語に翻訳されています。
http://developer.apple.com/ja/technotes/tn2028.html
copyright 2003 Ottimo, Inc. All rights reserved
無断転載・引用禁止
Contact us: koike@ottimo.co.jp