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

第 2 章 スレッドを使った基本プログラミング

スレッドライブラリ

この章では、POSIX スレッドライブラリ libpthread(3T) に入っている基本的なスレッドのプログラミングルーチンについて説明します。この章で説明するスレッドはデフォルトのスレッド (デフォルトの属性値をもつスレッド) です。マルチスレッドのプログラミングで最もよく使われるのがこの類のスレッドです。

第 3 章「スレッド生成時の属性設定」では、デフォルト以外の属性をもつスレッドの生成方法と使用方法を説明します。


注 -

属性はスレッド生成時にのみ指定されます。スレッドを使用中は変更できません。


この章で紹介する POSIX (libpthread) ルーチンのプログラミングインタフェースは、オリジナルの Solaris マルチスレッドライブラリ (libthread) のものと類似しています。

次の表は、特定のタスクとその説明が記載されているページを示しています。

デフォルトのスレッドの生成

属性オブジェクトを指定しなければ NULL となり、下記の属性をもつデフォルトスレッドが生成されます。

pthread_attr_init() でデフォルト属性オブジェクトを生成し、この属性オブジェクトを使ってデフォルトスレッドを生成することもできます。詳細は、「属性の初期化」の節を参照してください。

pthread_create(3T)

pthread_create() は、現在のプロセスに新しい制御スレッドを追加します。

プロトタイプ:
int	pthread_create(pthread_t *tid, const pthread_attr_t *tattr,
    void*(*start_routine)(void *), void *arg);
#include <pthread.h>

pthread_attr_t ()tattr;
pthread_t tid;
extern void *start_routine(void *arg);
void *arg;
int ret; 

/* デフォルト動作 */
ret = pthread_create(&tid, NULL, start_routine, arg);

/* デフォルト属性による初期化 */
ret = pthread_attr_init(&tattr);
/* デフォルト動作指定 */
ret = pthread_create(&tid, &tattr, start_routine, arg);

必要な状態動作を持つ tattrpthread_create() 関数が呼び出されます。start_routine は新しいスレッドで実行する関数です。start_routine が復帰すると、スレッドは終了状態を start_routine で戻される値に設定して終了します (詳細は、「pthread_create(3T) 」を参照してください)。

pthread_create() が正常終了すると、生成されたスレッドの識別子が tid の指す記憶場所に格納されます。

スレッドの生成で属性引数として NULL を使用するのは、デフォルト属性を使用するのと同じ効果があります。どちらの場合もデフォルトのスレッドが生成されます。tattr は初期化されると、デフォルト動作を獲得します。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると pthread_create() は失敗し、対応する値を戻します。


EAGAIN

システム制限を超えました。たとえば、生成する LWP が多すぎます。


EINVAL

tattr の値が無効です。

スレッドの終了待ち

pthread_join(3T)

pthread_join() 関数は、スレッドの終了を待ちます。

プロトタイプ:
int	pthread_join(thread_t tid, void **status);
#include <pthread.h>

pthread_t tid;
int ret;
int status;

/* スレッド「tid」の終了待ち、status の指定あり */
ret = pthread_join(tid, &status);

/* スレッド「tid」の終了待ち、status の指定は NULL */
ret = pthread_join(tid, NULL); 

pthread_join() 関数は、指定したスレッドが終了するまで呼び出しスレッドをブロックします。

指定するスレッドは、現在のプロセス内のスレッドで、しかも切り離されていないものでなければなりません。スレッドの切り離しについては、「切り離し状態の設定」を参照してください。

status が NULL でなければ、pthread_join() の正常終了時に status の指す記憶場所に終了したスレッドの終了状態が格納されます。

複数のスレッドが、同じスレッドの終了を待つことはできません。そのような状態が発生すると、あるスレッドは正常に戻りますが、他のスレッドは ESRCH エラーを戻し失敗します。

pthread_join() の復帰後は、そのスレッドに関連付けられていたスタック領域がそのアプリケーションで再利用できるようになります。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、pthread_join() は失敗し、次の値を戻します。


ESRCH

tid で指定したスレッドは、現在のプロセス内の切り離されていない正しいスレッドでありません。


EDEADLK

tid に呼び出しスレッドを指定しました。


EINVAL

tid の値が無効です。

pthread_join() ルーチンの引数は 2 つあり、ある程度柔軟な使い方ができます。特定のスレッドが終了するまで待つ場合は、そのスレッドの識別子を第 1 引数として指定します。

終了したスレッドの終了コードを調べたい場合は、それを受け取る領域のアドレスを指定します。

pthread_join() は、切り離されていないスレッドに対してだけ有効であることに注意してください。終了時のタイミングで特に同期をとる必要がないスレッドは、切り離して生成してください。

切り離されたスレッドが頻繁に使用するスレッドであり、切り離されていないスレッドは特に必要な場合に限って使用するものと考えてください。

簡単なスレッドの例

例 2-1 では、あるスレッドが最上位の手続きを実行し、手続き fetch() を実行する補助スレッドを生成します。手続き fetch() は複雑なデータベース検索を行い、処理に多少時間がかかります。

メインスレッドでは検索結果も必要ですが、その間に行うべき処理があります。そこで必要な処理を行なってから、pthread_join() で補助スレッドの終了を待ちます。

新しいスレッドへの引数 pbe がスタックパラメタとして渡されます。これが可能なのは、メインスレッドが自分の子スレッドの終了を待つからです。通常は、malloc(3C) でヒープから領域を確保する方が、スレッドのスタック領域で (スレッドが終了した場合なくなるか、再度割り当てられる) アドレスを受け渡すよりもよいでしょう。


例 2-1 簡単なスレッドプログラム

void mainline (...)
{
        struct phonebookentry *pbe;
        pthread_attr_t tattr;
        pthread_t helper;
        int status;

        pthread_create(&helper, NULL, fetch, &pbe);

            /* この間、他の処理を行う */

        pthread_join(helper, &status);
        /* ここでは結果を確実に使用できる */
}

void fetch(struct phonebookentry *arg)
{
        struct phonebookentry *npbe;
        /* データベースから値を取り出す */

        npbe = search (prog_name)
            if (npbe != NULL)
                *arg = *npbe;
        pthread_exit(0);
}   

struct phonebookentry {
        char name[64];
        char phonenumber[32];
        char flags[16];
}

スレッドの切り離し

pthread_detach(3T)

pthread_detach(3T) は、detachstate 属性を PTHREAD_CREATE_JOINABLE に設定して生成されたスレッドの記憶領域を再利用するための、pthread_join(3T) に代わるもう 1 つの方法です。

プロトタイプ:
int	pthread_detach(thread_t tid);
#include <pthread.h>

pthread_t tid;
int ret;

/* スレッド tid を切り離す */
ret = pthread_detach(tid); 

pthread_detach() 関数は、スレッド tid のための記憶領域がそのスレッドの終了時に再利用できることを、この実装に対して示すために使われます。tid が終了していない場合、pthread_detach() によって、そのスレッドが終了することはありません。同じスレッドに対して複数の pthread_detach() 呼び出しが行われたときの効果は不定です。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、pthread_detach() は失敗し、対応する値を戻します。


EINVAL

tid は、有効なスレッドではありません。


ESRCH

tid は、現在のプロセスの中の有効な切り離されていないスレッドではありません。

スレッド固有データ用キーの作成

シングルスレッドの C プログラムでは、データは局所データと広域データという 2 つの基本的なクラスに分類されます。一方、マルチスレッドの C プログラムでは、これに第 3 のクラスであるスレッド固有データ (Thread-Specific Data (TSD)) が追加されます。これは広域データと似ていますが、スレッドごとの専用のデータである点が異なります。

スレッド固有データ (TSD) は、スレッド単位で維持管理されます。TSD は、特定のスレッド固有のデータを定義し参照する唯一の手段となります。スレッド固有データの各項目は、プロセス内のすべてのスレッドから参照可能な特定のキー (key) と関連付けられます。そのキーを使用することによって、スレッドはスレッド単位で維持管理されるポインタ (void *) にアクセスできます。

pthread_key_create(3T)

pthread_key_create() は、プロセス内のスレッド固有データを識別するためのキーを割り当てます。このキーはプロセス内のすべてのスレッドから参照可能で、すべてのスレッドでそのキーが作成された時点では、初期値として NULL が関連付けられています。

pthread_key_create() は、キーの使用前にキーごとに 1 回呼び出されます。暗黙の同期はありません。

作成されたキーに対して、各スレッドは特定の値を結び付けることができます。その値はスレッドに固有で、スレッドごとに独立に維持管理されます。スレッド単位での割り当ては、キーがデストラクタ関数 (destructor()) で作成された場合は、スレッドの終了時にその割り当てを解除されます。

プロトタイプ:
int	pthread_key_create(pthread_key_t *key,
    void (*destructor) (void *));
#include <pthread.h>

pthread_key_t key;
int ret;

/* デストラクタを指定しないキーの作成 */
ret = pthread_key_create(&key, NULL);

/* デストラクタを指定したキーの作成 */
ret = pthread_key_create(&key, destructor); 

pthread_key_create() が正常終了すると、割り当てられたキーは key が指す位置に格納されます。このキーに対する記憶領域とアクセスとの同期は呼び出し側の責任でとらなければなりません。

各キーに任意で、デストラクタ関数を関連付けることができます。あるキーが NULL でないデストラクタ関数を持っていて、スレッドがそのキーに対して NULL 以外の値を関連付けている場合、そのスレッドの終了時に現在関連付けられている値を指定してデストラクタ関数が呼び出されます。どの順番でデストラクタ関数が呼び出されるかは不定です。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると pthread_key_create() は失敗し、次の値を戻します。


EAGAIN

キーの名前空間が使い果たされました。


ENOMEM

仮想記憶が足りないので、新しいキーを作成できません。

スレッド固有データキーの削除

pthread_key_delete(3T)

pthread_key_delete() は、既存のスレッド固有データキーを削除します。キーに関連付けられているどのメモリーも解放されます。これはキーが無効で、参照されていたらエラーが戻されるためです。Solaris スレッドには、これに相当する関数はありません。

プロトタイプ:
int	pthread_key_delete(pthread_key_t key);
#include <pthread.h>

pthread_key_t key;
int ret;

/* 前に作成されたキー */
ret = pthread_key_delete(key); 

キーが削除された後、pthread_setspecific() または pthread_getspecific() 呼び出しでそのキーが参照されると、EINVAL エラーが戻されます。

削除関数を呼び出す前にスレッド固有の資源を解放するのは、プログラマの責任です。この関数はデストラクタ関数をいっさい呼び出しません。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると pthread_key_delete() は失敗し、対応する値を戻します。


EINVAL

key の値が有効ではありません。

スレッド固有データキーの設定

pthread_setspecific(3T)

pthread_setspecific() は、スレッド固有な割り当てを、使用したスレッド固有データキーに設定します。

プロトタイプ:
int	pthread_setspecific(pthread_key_t key, const void *value);
#include <pthread.h>

pthread_key_t key;
void *value;
int ret;

/* 前に作成されたキー */
ret = pthread_setspecific(key, value); 

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると pthread_setspecific() は失敗し、対応する値を戻します。


ENOMEM

仮想記憶が足りません。


EINVAL

キーが無効です。


注 -

pthread_setspecific() スレッドがすでに使用しているキーに対してそのスレッドの新しい割り当てを設定した場合は、メモリーリークが発生する可能性があります。


スレッド固有データキーの取得

pthread_getspecific(3T)

pthread_getspecific() は、key についての呼び出しスレッドの割り当てを取得し、それを value が指している記憶場所に格納します。

プロトタイプ:
int	pthread_getspecific(pthread_key_t key);
#include <pthread.h>

pthread_key_t key;
void *value;

/*  前に作成されたキー */
value = pthread_getspecific(key); 

戻り値

エラーは戻されません。

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

例 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 の参照は、スレッドごとに異なる記憶領域を参照します。

変数 mywindow は、それを参照するスレッドの専用のウィンドウに接続される stdio ストリームを参照するための変数です。errno と同様、スレッドによる mywindow の参照は、スレッドごとに異なる記憶領域、つまり異なるウィンドウを参照します。唯一の違いは、errno はスレッドライブラリが面倒を見てくれるのに対し、mywindow はプログラマが自分で管理しなければならないことです。

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

このルーチンは pthread_getspecific() を呼び出し、広域変数 mywindow_key (これは実際の広域変数) と出力用のパラメタ win を渡します。win には、そのスレッドのウィンドウの識別子が戻されます。


例 2-3 広域参照から局所参照への変換

thread_key_t mywin_key;

FILE *_mywindow(void) {
    FILE *win;

    pthread_getspecific(mywin_key, &win);
    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_keycreate(&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() を呼び出して上記の広域キーを渡します。その結果、スレッドが pthread_setspecific() を呼び出して、このキーに関連付けた値を取得できます。

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

スレッド識別子の取得

pthread_self(3T)

pthread_self() は、呼び出しスレッドの識別子を取得します。

プロトタイプ:
pthread_t	 pthread_self(void);
#include <pthread.h>

pthread_t tid;

tid = pthread_self();

戻り値

呼び出しスレッドの識別子が戻されます。

スレッド識別子の比較

pthread_equal(3T)

pthread_equal() は、2 つのスレッドのスレッド識別番号を比較します。

プロトタイプ:
int	 pthread_equal(pthread_t tid1, pthread_t tid2);
#include <pthread.h>

pthread_t tid1, tid2;
int ret;

ret = pthread_equal(tid1, tid2);

戻り値

tid1tid2 が等しければ 0 以外の値が戻されます。そうでなければ、0 が戻されます。tid1 または tid2 が無効なスレッド識別番号の場合は、結果は予測できません。

スレッドの初期化

pthread_once(3T)

pthread_once() は、初めて呼び出されたときに初期化ルーチンを呼び出します。2 回目以降の pthread_once() 呼び出しは何の効果もありません。

プロトタイプ:
int	 pthread_once(pthread_once_t *once_control,
    void (*init_routine)(void));
#include <pthread.h>

pthread_once_t once_control = PTHREAD_ONCE_INIT;
int ret;

ret = pthread_once(&once_control, init_routine);

once_control パラメタは、該当する初期化ルーチンがすでに呼び出されているかどうかを判定します。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると pthread_once() は失敗し、対応する値を戻します。


EINVAL

once_control または init_routine が NULL です。

スレッドの実行明け渡し

sched_yield(3R)

sched_yield() は、現在のスレッドから同じ優先順位か、より高い優先順位をもつ別のスレッドに実行権を譲ります。

プロトタイプ:
int	 sched_yield(void);
#include <sched.h>

int ret;

ret = sched_yield();

戻り値

正常終了時は 0 です。そうでなければ -1 が戻され、errno にエラー条件が設定されます。


ENOSYS

この実装では、sched_yield(3R) はサポートされていません。

スレッド優先順位の設定

pthread_setschedparam(3T)

pthread_setschedparam() は、既存のスレッドの優先順位を変更します。この関数はスケジューリング方針には影響を与えません。

プロトタイプ:
int	 pthread_setschedparam(pthread_t tid, int policy,
    const struct sched_param *param);
#include <pthread.h>

pthread_t tid;
int ret;
struct sched_param param;
int priority;

/* sched_priority がスレッドの優先順位になる */
sched_param.sched_priority = priority;

/* サポートされている方針のみ。それ以外は ENOTSUP を生じる */
policy = SCHED_OTHER;

/* 対象スレッドのスケジューリングパラメタ */
ret = pthread_setschedparam(tid, policy, &param); 

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると、この関数は失敗し、対応する値を戻します。


EINVAL

設定しようとした属性の値が無効です。


ENOTSUP

サポートされていない属性値を設定しようとしました。

スレッド優先順位の取得

pthread_getschedparam(3T)

pthread_getschedparam() は、既存のスレッドの優先順位を取得します。

プロトタイプ:
int	 pthread_getschedparam(pthread_t tid, int policy,
    struct schedparam *param);
#include <pthread.h>

pthread_t tid;
sched_param param;
int priority;
int policy;
int ret;

/* 対象スレッドのスケジューリングパラメタ */
ret = pthread_getschedparam (tid, &policy, &param);

/* sched_priority にスレッドの優先順位が含まれる */
priority = param.sched_priority; 

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を戻します。


ESRCH

tid で指定した値が既存のスレッドを表していません。

シグナルのスレッドへの送信

pthread_kill(3T)

pthread_kill() は、スレッドにシグナルを送ります。

プロトタイプ:
int	 pthread_kill(thread_t tid, int sig);
#include <pthread.h>
#include <signal.h>
int sig;
pthread_t tid;
int ret;

ret = pthread_kill(tid, sig);

tid で指定したスレッドに sig で指定したシグナルを送ります。tid は、呼び出しスレッドと同じプロセス内のスレッドでなければなりません。引数 sig は、signal(5) のリスト中の値でなければなりません。

sig が 0 のときはエラーチェックだけが行われ、シグナルは実際には送られません。これにより tid で指定したスレッド識別子が有効であるかどうかを調べることができます。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下のいずれかの条件が検出されると pthread_kill() は失敗し、次の値が戻されます。


EINVAL

sig は正しいシグナル番号ではありません。


ESRCH

現在のプロセス内で tid で指定したスレッドが見つかりません。

呼び出しスレッドのシグナルマスクの変更

pthread_sigmask(3T)

pthread_sigmask() は、呼び出しスレッドのシグナルマスクの変更や照会を行います。

プロトタイプ:
int	pthread_sigmask(int how, const sigset_t *new, sigset_t *old);
#include <pthread.h>
#include <signal.h>
int ret;
sigset_t old, new;

ret = pthread_sigmask(SIG_SETMASK, &new, &old); /* 新しいマスクを設定する */
ret = pthread_sigmask(SIG_BLOCK, &new, &old); /* マスクをブロックする */
ret = pthread_sigmask(SIG_UNBLOCK, &new, &old); /* マスクのブロックを解除する */

引数 how は、シグナルマスクの変更方法を指定します。以下のいずれかの値を指定できます。

new の指定が NULL の場合、how の値は無視され、スレッドのシグナルマスクは変更されません。現在ブロックされているシグナルを照会するときは、引数 new の値に NULL を指定してください。

old の指定が NULL でなければ、old の指すアドレスに変更前のシグナルマスクが格納されます。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると pthread_sigmask() は失敗し、次の値が戻されます。


EINVAL

how の値が定義されていません。

安全な fork

pthread_atfork(3T)

「解決策 − pthread_atfork(3T)」pthread_atfork()の説明を参照してください。

プロトタイプ:

int pthread_atfork(void (*prepare) (void), void (*parent) (void),
    void (*child) (void) );

スレッドの終了

pthread_exit(3T)

pthread_exit() は、スレッドを終了させます。

プロトタイプ:
void	 pthread_exit(void *status);
#include <pthread.h>

int status;

pthread_exit(&status); /* status を示して終了 */

pthread_exit() は呼び出しスレッドを終了させます。スレッド固有に割り当てられているデータもすべて解放されます。スレッドが切り離されていない場合は、そのスレッドに対する (ブロック化される) 終了待ちが行われるまで、そのスレッド識別子と status により示される終了状態は保持されます。(ブロック化される) スレッドが切り離されている場合は、status は無視され、そのスレッド識別子がただちに再利用できるようになります。スレッドの切り離しについては、「切り離し状態の設定」を参照してください。

戻り値

status の指定が NULL でなければ、呼び出しスレッドが終了すると、終了状態が status の内容に設定されます。

スレッド終了処理の完了

スレッドの終了には下記の方法があります。

デフォルトでは、他のスレッドが当該スレッドに対して「終了待ち」を行い、その消滅を確認するまでの間、スレッドは残存します。これはデフォルトの pthread_create() 生成属性の「切り離されていない」と同じです (詳細は、pthread_detach(3T) のマニュアルページを参照してください)。「終了待ち」操作が行われると当該スレッドの終了状態が取得され、その後、当該スレッドが消滅します。

特に注意すべき特別な場合があります。メインスレッド (すなわち、main() を呼んでいるもの) が main() 呼び出しから戻るか、exit(3C) を呼び出す場合です。この操作が行われるとプロセス全体が終了し、プロセス内のスレッドもすべて終了してしまいます。このため、メインスレッドが main() から処理途中で戻ることがないよう十分注意しなければなりません。

メインスレッドが単に pthread_exit(3T) を呼び出した場合は、メインスレッドが終了するだけです。プロセス内の他のスレッドとプロセスは、その後も存続します。(すべてのスレッドが終了するとプロセスは終了します。)

取り消し

POSIX スレッドは、スレッドプログラミングに取り消し可能性 (取り消し機能) という考え方を導入しました。取り消し機能を使用することによって、スレッドはそのプロセスの他の任意のスレッドまたは全スレッドを終了させることができます。関連のある一群のスレッドの以降の操作がすべて有害または不必要な状況では、取り消しは 1 つの有効な方法です。好ましい方法は、すべてのスレッドを取り消し、そのプロセスを矛盾のない状態に戻してから起点まで戻ることです。

スレッドの取り消しの例としては、非同期的に生成される取り消し条件、たとえば実行中のアプリケーションを閉じるまたは終了するというユーザの要求などがあります。また、複数のスレッドが関わっているタスクの完了などもあります。最終的にスレッドの 1 つがそのタスクを完了させたのに、他のスレッドが動作し続けている場合は、その時点でそれらのスレッドは何の役にも立っていないため、すべて取り消したほうがよいでしょう。

取り消しには危険が伴います。そのほとんどは、不変式の復元と共有資源の解放処理に関係します。不注意に取り消されたスレッドは mutex をロック状態のままにすることがあり、その場合はデッドロックを引き起こします。あるいは、どこか特定できないメモリー領域を割り当てられたままにすることもあるので解放できなくなります。

pthread ライブラリでは、取り消しをプログラムにより許可したり禁止したりする取り消しインタフェースを規定しています。ライブラリでは、どの点で取り消しが可能かを示す一群のポイント (取り消しポイント) も定義しています。さらに、取り消しハンドラ (クリーンアップサービスを提供する) の有効範囲を定義して、意図した時と場所に確実に働くようにできます。

取り消しポイントの配置と取り消しハンドラの効果は、アプリケーションに対する理解に基づくものでなければなりません。mutex は明らかに取り消しポイントではないので、ロックしている時間は必要最小限に留めるべきです。

非同期取り消しの領域は、宙に浮いた資源や未解決の状態を生じさせるような外部に依存しないシーケンスに限定してください。入れ子の代替取り消し状態から復帰するときは、取り消し状態を復元するように注意してください。このインタフェースは、復元を容易に行えるように次の機能を提供しています。pthread_setcancelstate(3T) は、参照される変数の中に現在の取り消し状態を保存します。pthread_setcanceltype(3T) は、これと同じ方法で現在の取り消しタイプを保存します。

取り消しが起こりうる状況は、次の 3 通りです。

デフォルトでは、取り消しが起こりうるのは POSIX 規格で定義されているような、明確に定義されたポイントに限られます。

いずれの場合も、資源と状態が起点と矛盾しない状態に復元されるように注意してください。

取り消しポイント

スレッドの取り消しは、取り消しが安全な場合にだけ行なってください。pthread 規格では、下記のような取り消しポイントが規定されています。

デフォルトでは、取り消しが有効 (使用可能) です。アプリケーションで取り消しを無効 (使用不可) にした場合は、再び有効にするまで、すべての取り消し要求が据え置かれます。

取り消しを無効にする方法については、「pthread_setcancelstate(3T)」を参照してください。

スレッドの取り消し

pthread_cancel(3T)

pthread_cancel() は、スレッドを取り消します。

プロトタイプ:

int	pthread_cancel(pthread_t thread);
#include <pthread.h>

pthread_t thread;
int ret;

ret = pthread_cancel(thread);

取り消し要求がどのように扱われるかは、対象となるスレッドの状態によって異なります。その状態を判定する関数として、pthread_setcancelstate(3T)pthread_setcanceltype(3T) の 2 つがあります。

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を戻します。


ESRCH

指定されたスレッド ID に対応するスレッドが見つかりません。

取り消しを有効または無効にする

pthread_setcancelstate(3T)

pthread_setcancelstate() は、スレッドの取り消し機能を有効 (使用可能) または無効 (使用不可) にします。スレッドが生成されると、デフォルトでは取り消し機能が有効になります。

プロトタイプ:

int	pthread_setcancelstate(int state, int *oldstate);
#include <pthread.h>

int oldstate;
int ret;

/* 有効にする */
ret = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate);

/* 無効にする */
ret = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると pthread_setcancelstate() は失敗し、対応する値を戻します。


EINVAL

状態が PTHREAD_CANCEL_ENABLE でも PTHREAD_CANCEL_DISABLE でもありません。

取り消しタイプの設定

pthread_setcanceltype(3T)

pthread_setcanceltype() は、取り消しタイプを遅延モードまたは非同期モードに設定します。スレッドが生成されると、デフォルトでは取り消しタイプが遅延モードに設定されます。遅延モードにあるスレッドは、取り消しポイント以外では取り消すことができません。非同期モードにあるスレッドは、実行中の任意のポイントで取り消すことができます。非同期モードを使用するのは好ましくありません。

プロトタイプ:

int	pthread_setcanceltype(int type, int *oldtype);
#include <pthread.h>

int oldtype;
int ret;

/* 遅延モード */
ret = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);

/* 非同期モード */
ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype);

戻り値

正常終了時は 0 です。それ以外の戻り値は、エラーが発生したことを示します。以下の条件が検出されると、この関数は失敗し、対応する値を戻します。


EINVAL

PTHREAD_CANCEL_DEFERRED または PTHREAD_CANCEL_ASYNCHRONOUS タイプではありません。

取り消しポイントの設定

pthread_testcancel(3T)

pthread_testcancel() は、スレッドの取り消しポイントを設定します。

プロトタイプ:

void pthread_testcancel(void);
#include <pthread.h>

pthread_testcancel(); 

pthread_testcancel() 関数が実際に機能するのは、取り消し機能が有効にされていて、しかも遅延モードになっているときです。取り消し機能が無効になっている状態で、この関数を呼び出しても何の効果もありません。

pthread_testcancel() を挿入するのは、スレッドを取り消しても安全なシーケンスに限定してください。pthread_testcancel() 呼び出しを通してプログラムで設定される取り消しポイントの他にも、pthread 規格では、いくつかの取り消しポイントが規定されています。詳細は、「取り消しポイント」を参照してください。

戻り値はありません。

スタックへハンドラをプッシュする

クリーンアップハンドラは、割り当てられた資源のクリーンアップや不変式の復元など、諸条件を起点のものと矛盾しない状態に復元するためのものです。クリーンアップハンドラの管理には、pthread_cleanup_push(3T) 関数と pthread_cleanup_pop(3T) 関数を使用します。

クリーンアップハンドラは、プログラムの同じ字句解析上の範囲でプッシュされてポップされます。プッシュとポップは、常に対になっていなければなりません。そうでないと、コンパイルエラーになります。

pthread_cleanup_push(3T)

pthread_cleanup_push() 関数は、クリーンアップハンドラをクリーンアップスタック (LIFO) にプッシュします。

プロトタイプ:

void pthread_cleanup_push(void(*routine)(void *), void *args);
#include <pthread.h>

/* ハンドラ「routine」をクリーンアップスタックにプッシュする */
pthread_cleanup_push (routine, arg); 

スタックからハンドラを取り出す

pthread_cleanup_pop(3T)

pthread_cleanup_pop() 関数は、クリーンアップハンドラをクリーンアップスタックから取り出します。

この関数への引数が 0 以外なら、指定のハンドラがスタックから取り除かれて実行されます。引数が 0 の場合は、ハンドラはポップされるだけで実行されません。

0 以外の引数を指定して pthread_cleanup_pop を有効に呼び出せるのは、スレッドが pthread_exit(3T) を明示的または暗黙的に呼び出した場合か、取り消し要求を受け付けた場合です。

プロトタイプ:
void pthread_cleanup_pop(int execute);
#include <pthread.h>

/* 「func」をクリーンアップスタックからポップし、「func」を実行する */
pthread_cleanup_pop (1);

/* 「func」をポップするが、「func」を実行しない */
pthread_cleanup_pop (0); 

戻り値はありません。