最終更新時刻 :

Macintosh でするマルチスレッドプログラミング

はじめに...

Macintosh をお使いの皆さん、「Mac でマルチスレッドプログラミングは出来無いのかなぁ...」などとお思いではありませんか。私はある時突然そう思いました。

そこで Thread Manager を勉強し始めました。日本語の文書が見つからなかったので、英文の pdf を辞書を片手にヨミヨミ。英語の苦手な私には、辛い毎日です。

このページでは、私が理解した事柄を書き綴っていこうと思っています。少しでも皆さんのマルチスレッドプログラミングのお役にたてたら幸いです。

さて、UNIX などでは POSIX スレッドというものが存在します。用意されている関数や変数は Mac のそれとは異なりますが、スレッドの概念(パラダイム、メンタルモデル)は同じです。本格的に勉強したい方はそちらで学ぶ事をお薦めいたします。

「つまらない話」の「Threadの心」 もよろしく。

さあ、やろう!

では始めましょう。ところで Macintosh のマルチスレッドは Thread Manager によりサポートされます。あなたのシステムはサポートしているでしょうか。もし無いならば、今すぐに Thread Manager をダウンロードして下さい。備わっているかどうか判らない方は、Threads.h ファイルを探して下さい。ファイルが見つからなければ無いと思いましょう。

スレッド生成の前に...

スレッドを生成する前に、下準備をします。ここではスレッド生成までに必要な手続きを示します。(注:[#]は行を表わします。)


  1. /* ... */
  2. #include <Threads.h>
  3. /* ... */
  4. pascal void *aThread(void *threadParameter);
  5. /* ... */
  6. int main(void)
  7. {
  8. long feature;
  9. OSErr err;
  10. /* ... */
  11. MaxApplZone();
  12. /* ... */
  13. err = Gestalt(gestaltThreadMgrAttr, &feature);
  14. if (err != noErr) {
  15. /* Gestalt()の呼び出しに失敗した... */
  16. } else if (!(feature & (1L << gestaltThreadMgrPresent))) {
  17. /* Thread Manager が働いていない... */
  18. }
  19. /* ... */
  20. }

準備はここまでです。

スレッドを生成しよう

Macintosh のスレッドタイプは Cooperative タイプです(MacOS 8.6 から Preemptive タイプがサポートされました。おめでとう!)。68k でなおかつ Toolbox を使わないか、MacOS 8.6 以降ならば Preemptive タイプも扱えますが、ここでは Cooperative タイプの短いコードを想定します。

まず、目的の関数をスレッド化する為に、NewThread 関数を呼びます。

    OSErr NewThread(
        ThreadStyle threadStyle,
        ThreadEntryProcPtr threadEntry,
        void *threadParam,
        Size stackSize,
        ThreadOptions options,
        void **threadResult,
        ThreadID *threadMade
    )
threadStyle
スレッド化する関数のタイプを代入します。今は Cooperative 型なので、kCooperativeThread を代入します。(もし Preemptive 型を望むなら、kPreemptiveThread を代入して下さい。)
threadEntry
スレッド化する関数へのポインタを代入します。pascal 型が良いです。NULL または 0 を代入したらどうなるんでしょう?
threadParam
その関数の引き数へのポインタを代入します。ですから、スレッド化する関数も、引き数は void * 型にしましょう。
stackSize
スレッドに使うスタックメモリのサイズを決めます。決めていない(決まっていない)場合は、0 を代入しましょう。
options
NewThread 関数のオプションです。新しくスレッドを作る場合(よく分からない場合)は、kCreateIfNeeded を代入してみましょう。
threadResult
スレッド化した関数の返り値が代入されます。スレッド化した関数が終了した時、結果がここに入れられます。ハンドルを渡して下さい。ただし、ハンドルの先は用意しないように気をつけて下さい。返される必要が無い場合は、NULL か 0 を代入しましょう。
threadMade
その関数に与えられたスレッドIDを返します。スレッド化に失敗した時は、kNoThreadID が代入されます。

これだけです。簡単。

さて、スレッド化される関数についてです。特に途中でしなければならない処理はありません。しかし Cooperative 型では、他のスレッドにプロセッサリソース(CPU)を渡すことを、明示的に言わなければなりません。そうしないと、いつまでもプロセッサリソースを一人占めしてしまいます。マルチスレッドの意味があまりありませんね。そこで、処理が一つ済むごとに次にあげる関数を呼ぶようにしましょう。とくに main 関数でスレッドを作っておきながら、プロセッサリソース(CPU)を渡さないとどうなるでしょうか。スレッドに処理が渡らず、そのまま終わってしまいます。気を付けて下さい。

    OSErr YieldToAnyThread(
        void
    )

自分自身のスレッドIDが何かを知る為には、GetCurrentThread 関数を使います。main 関数の中でも使えます。main も立派なスレッドですから。

    OSErr GetCurrentThread(
        ThreadID *currentThreadID
    )
currentThreadID
自分のスレッドIDが代入されます。

終了するときは、そのまま return で終わります。特定のスレッドを終了させるには、そのスレッドIDを次の関数に渡します。

    OSErr DisposeThread(
        ThreadID threadToDump,
        void *threadResult,
        Boolean recycleThread
    )
threadToDump
終了させるスレッドのスレッドIDを代入します。ただし、main 関数のスレッドIDは渡せません。main 関数はスレッドなのに殺せないのです。Mac のスレッドの弱いところ。
threadResult
返り値へのポインタを代入します。くれぐれも局所変数のアドレスなどを入れないように。この値は NewThread 関数の6番目の引き数(ハンドル)に渡されます。
recycleThread
自分がいなくなった後、自分のいた場所(つまりメモリの事)をどうするかを指示します。ガーベッジコレクションの様な物。true ならばコレクション(スレッドプール)に貯え、false ならばメモリを解放します。わからなければ false にしましょう。

これでおしまいです。これらを使って、簡単な例を作ってみました。C 版と C++ 版を用意いたしました。どうぞ、読んで実行してみて下さい。

Preemptive Multitask もやってみたいのですが、まだ私に知識と技術がないのです。残念。さらに進んでやってみたい方は、POSIX スレッド(pthread)の本を読むか、Mac の配付している Thread Manager についている pdf を読んでみて下さい。

以上です。ありがとうございました。