マルチスレッドのプログラミング

スレッド固有データの大域性と局所性の例

例 2–2 は、あるマルチスレッドプログラムの抜粋です。このコードは任意の数のスレッドによって実行されますが、コードでは 2 つの大域変数 errnomywindow を参照しています。これらの大域値は、実際には各スレッド専用の項目への参照になります。


例 2–2 スレッド固有データの大域性と局所性

body() {
    ...

    while (write(fd, buffer, size) == -1) {
        if (errno != EINTR) {
            fprintf(mywindow, "%s\n", strerror(errno));
            exit(1);
        }
    }

    ...

}

errno を参照すれば、そのスレッドが呼び出したルーチンから戻されたシステムエラーコードがわかります。ほかのスレッドが呼び出したシステムコールではありません。ヘッダーファイル errno.h をインクルードすると、errno の参照が errno のスレッド専用インスタンスの参照になります。これにより、あるスレッドによる errno の参照が、ほかのスレッドによる errno の参照とは別の記憶領域の場所を参照するようになります。

変数 mywindow は、それを参照するスレッドの専用のウィンドウに接続される stdio ストリームを参照するための変数です。したがって、errno と同様に、あるスレッドによる mywindow の参照は、ほかのスレッドによる mywindow の参照とは別の記憶領域を参照します。最終的には、この参照は異なるウィンドウへの参照になります。ここでの唯一の違いは、errno はシステムによって処理されるのに対し、mywindow はプログラマが処理しなければならない点です。

次の例は、mywindow の参照がどのように働くかを示しています。プリプロセッサは、mywindow の参照を _mywindow() 手続きの呼び出しに変換します。

このルーチンは、 pthread_getspecific() を呼び出し、pthread_getspecific() に、大域変数 mywindow_key と、このスレッドのウィンドウの識別情報が入る出力パラメータ win を渡します。


例 2–3 大域参照から局所参照への変換

thread_key_t mywin_key; 

FILE *_mywindow(void) {
 FILE *win;
 win = pthread_getspecific(mywin_key);
 return(win);
 }
#define mywindow _mywindow()

void routine_uses_win( FILE *win) {
 ... 
} 
void thread_start(...) {
 ... 
 make_mywin();
 ... 
 routine_uses_win( mywindow )
 ... 
}

変数 mywin_key は、スレッドごとに実体を持つことができる変数のまとまりを識別します。つまり、これらの変数はスレッド固有データです。各スレッドは make_mywin() を呼び出して自分専用のウィンドウを初期化し、スレッド固有データの参照用に自分専用の mywindow のインスタンスを配置します。

make_mywin を呼び出したスレッドは、mywindow を安全に参照できるようになり、さらに _mywindow() の実行後は、自分専用のウィンドウを参照できるようになります。結果的に、mywindow の参照は、そのスレッドの専用のデータの直接の参照であるかのように見えます。

例 2–4 は、参照の設定方法を示しています。


例 2–4 スレッド固有データの初期化

void make_mywindow(void) {
    FILE **win;
    static pthread_once_t mykeycreated = PTHREAD_ONCE_INIT;

    pthread_once(&mykeycreated, mykeycreate);

    win = malloc(sizeof(*win));
    create_window(win, ...);

    pthread_setspecific(mywindow_key, win);
}

void mykeycreate(void) {
    pthread_key_create(&mywindow_key, free_key);
}

void free_key(void *win) {
    free(win);
}

まず最初に、mywin_key キーに一意的な値を取得します。これはスレッド固有データのクラスを識別するために使用するキーです。具体的には、make_mywin() を呼び出す最初のスレッドが pthread_key_create() を呼び出します。その結果、この関数の第 1 引数に一意なキーが割り当てられます。第 2 引数はデストラクタ関数で、このスレッド固有データ項目のスレッド専用インスタンスをスレッドの終了時に解放するためのものです。

次に、呼び出し側の、このスレッド固有データ項目のインスタンスのために記憶領域を確保します。その後、create_window() を呼び出して、スレッド用にウィンドウを設定します。win は、ウィンドウに割り当てられた記憶領域をポイントします。最後に pthread_setspecific() が呼び出され、win とキーとが結び付けられます。

続いて、スレッドは pthread_getspecific() を呼び出して、上記の大域キー (key) を渡します。その結果、スレッドは、以前の pthread_setspecific() 呼び出しでこのキーに関連付けた値を取得できます。

スレッドが終了するときは、pthread_key_create() で設定したデストラクタ関数が呼び出されます。各デストラクタ関数は、そのスレッドが pthread_setspecific() でキーに値を設定している場合だけ呼び出されます。