スレッドの終了

スレッドは、そのエントリー・ポイント・ルーチンから戻ると、自動的に終了します。

スレッドは、取り消し と呼ばれるメカニズムを使用して、スレッド自体を明示的に終了することも、 プロセス内の他の任意のスレッドを終了させることもできます。 スレッドはすべて同じデータ・スペースを共用するので、 終了時にクリーンアップ操作を実行する必要があります。この目的のために、 スレッド・ライブラリーはクリーンアップ・ハンドラーを提供します。

スレッドの終了

プロセスは、スレッドが exit サブルーチンを呼び出すことにより、 いつでも終了させることができます。 同様に、スレッドは、pthread_exit サブルーチンを呼び出すことによって、いつでも終了させることができます。

exit サブルーチンを呼び出すと、 そのすべてのスレッドを含めて、プロセス全体が終了します。 マルチスレッド・プログラムでは、exit サブルーチンは、プロセス全体を終了する必要がある場合、例えばリカバリー不能エラーの場合などに限って使用すべきです。初期スレッドの終了の場合でも、 pthread_exit サブルーチンの方を使用すべきです。

pthread_exit サブルーチンを呼び出すと、 コール側のスレッドが終了します。 status パラメーターは、ライブラリーによって保存されていて、 さらに終了したスレッドを結合するときに使用することができます。 pthread_exit サブルーチンの呼び出しは、スレッドの初期ルーチンから戻る場合と似ていますが、 まったく同じではありません。 スレッドの初期ルーチンから戻ったときの結果は、スレッドに応じて次のように決まります。

  • 初期スレッドから戻るときは、 暗黙的に exit サブルーチンを呼び出し、 そのことによってプロセス内のすべてのスレッドを終了させます。
  • 他のスレッドから戻るときは、 pthread_exit サブルーチンを暗黙的に呼び出します。 戻り値には、pthread_exit サブルーチンの status パラメーターと同じ役割があります。

暗黙的に exit サブルーチンを呼び出すことを避けるために、pthread_exit サブルーチンを使用してスレッドを終了してください。

初期スレッドを終了しても (例えば、main ルーチンから pthread_exit サブルーチンを呼び出して)、プロセスは終了しません。 終了するのは、初期スレッドだけです。 初期スレッドが終了した場合、プロセスは、 その最後のスレッドが終了したときに終了します。 この場合、プロセスの戻りコードは 0 です。

次のプログラムは、ちょうど 10 個のメッセージをそれぞれの言語で表示します。 これは、main ルーチンで 2 つのスレッドを作成した後で pthread_exit サブルーチンを呼び出して、 Thread ルーチンでループを作成することによって行います。

#include <pthread.h>    /* include file for pthreads - the 1st */
#include <stdio.h>      /* include file for printf()           */

void *Thread(void *string)

{
        int i;
 
        for (i=0; i<10; i++)
                printf("%s¥n", (char *)string);
        pthread_exit(NULL);
}

int main()
{
        char *e_str = "Hello!";
        char *f_str = "Bonjour !";
 
        pthread_t e_th;
        pthread_t f_th;
 
        int rc;
 
        rc = pthread_create(&e_th, NULL, Thread, (void *)e_str);
        if (rc)
                exit(-1);
        rc = pthread_create(&f_th, NULL, Thread, (void *)f_str);
        if (rc)
                exit(-1);
        pthread_exit(NULL);
}

pthread_exit サブルーチンは、 スレッドのスタックを含めて、スレッド固有データをすべて解放します。 スタックが解放されるので、スタック上に割り当てられた任意のデータが無効になり、 対応するメモリーは他のスレッドが再使用することができます。 したがって、 スレッドのスタック上に割り当てられたスレッド同期オブジェクト (mutex と条件変数) は、 スレッドが pthread_exit サブルーチンを呼び出す前に破棄する必要があります。

exit サブルーチンと異なり、 pthread_exit サブルーチンはスレッド間で共用されているシステム・リソースをクリーンアップしません。 例えば、ファイルは、 他のスレッドが使用する可能性があるので、pthread_exit サブルーチンではクローズされません。

スレッドの取り消し

スレッド取り消しメカニズムによって、スレッドは、 プロセス内の他の任意のスレッドの実行を、 制御された方法で終了させることができます。 ターゲット・スレッド (つまり、 取り消されようとしているスレッド) は、数多くの方法で取り消し要求保留を保留して、 取り消しの通知が行われた時点で、アプリケーション固有のクリーンアップ処理を実行することができます。 取り消されると、スレッドは暗黙的に pthread_exit((void *)-1) サブルーチンを呼び出します。

スレッドの取り消しは、 pthread_cancel サブルーチンを呼び出すことによって要求されます。 その呼び出しが戻ると、要求は登録されていますが、 スレッドはまだ実行中の可能性があります。 pthread_cancel サブルーチンに対する呼び出しが成功しないのは、 指定されたスレッド ID が無効な場合だけです。

取り消し可能の状態とタイプ

スレッドの取り消し可能の状態とタイプによって、取り消し要求を受け取ったときのアクションが決まります。 各スレッドが、取り消し可能の状態とタイプを、 pthread_setcancelstate サブルーチンと pthread_setcanceltype サブルーチンで制御しています。

次の表に示されているように、次の取り消し可能な状態と取り消し可能なタイプにより、3 つの場合が可能です。
取り消し可能の状態 取り消し可能のタイプ 結果
使用不可 任意 (タイプは無視される) 取り消し可能は無効
使用可能 据え置き 据え置きの取り消し可能
使用可能 非同期 非同期の取り消し可能
これらの場合の説明は、次のとおりです。
  • 取り消し可能は無効。 取り消し要求は、取り消し可能の状態が変更されるか、またはスレッドが他の方法で終了されるまで、 いずれも保留にセットされます。

    スレッドの取り消し可能が無効になるのは、割り込みできない操作を実行するときだけです。 例えば、スレッドがある種の複雑なファイル保存操作 (索引付きデータベースなど) を実行している場合、操作中に取り消されると、 ファイルが不整合状態のままになる可能性があります。 これを避けるために、ファイル保存操作中、 スレッドは取り消し可能を無効にする必要があります。

  • 据え置きの取り消し可能。 取り消し要求はいずれも、そのスレッドが次の取り消しポイントに達するまで保留になります。 これがデフォルトの取り消し可能の状態です。

    この取り消し可能の状態では、スレッドを確実に取り消すことができますが、 取り消しが可能なのは、スレッドを実行する際の特定のポイント (取り消しポイント と呼ばれる) に限られます。 取り消しポイントで取り消されたスレッドはシステムを安全な状態のままにしますが、 ユーザー・データが不整合になるか、 または取り消されたスレッドがロックを保持している場合があります。 このような状態を避けるには、クリーンアップ・ハンドラーを使用するか、または クリティカル領域内で取り消し可能を無効にしてください。 詳しくは、クリーンアップ・ハンドラーの使用を参照してください。

  • 非同期の取り消し可能。 任意の取り消し要求は、すぐに実行されます。

    リソースを保持している間に非同期的に取り消されたスレッドは、 プロセスまたはシステムでさえ、 リカバリーが困難または不可能な状態のままに放置してしまいます。 async-cancel セーフティーについて詳しくは、async-cancel セーフティーを参照してください。

async-cancel セーフティー

非同期の取り消し可能を有効にして関数を呼び出したときに、 どの任意の命令で取り消し要求が出されても、 どのリソースも破壊されないように作成された関数を、 async-cancel セーフ であるといいます。

副次的にリソースを取得する関数は、async-cancel セーフにすることができません。 例えば、malloc サブルーチンは、 非同期の取り消し可能が有効な状態で呼び出された場合、リソースの取得に成功して、 コール側に戻している途中で、取り消し要求の処置を取る可能性があります。 このような場合、プログラムにはリソースが取得されたかどうかを知る手段がありません。

このために、大部分のライブラリー・ルーチンは async-cancel セーフであるとは見なせません。 非同期の取り消し可能を使用するのは、リソースを保持しない操作だけを実行し、async-cancel セーフであるライブラリー・ルーチンを呼び出すだけである場合にすることをお勧めします。

次のサブルーチンは、async-cancel セーフであり、 非同期の取り消し可能が有効な場合でも、取り消しが確実に正しく処理されるようになります。

  • pthread_cancel
  • pthread_setcancelstate
  • pthread_setcanceltype

非同期の取り消し可能の代わりには、据え置きの取り消し可能を使用し、 pthread_testcancel サブルーチンを呼び出して明示的取り消しポイントを追加します。

取り消しポイント

取り消しポイントは、あるサブルーチンの内部のポイントで、 据え置きの取り消し可能が有効になっている場合、 ここで保留状態の取り消し要求があれば、スレッドはその要求を処理する必要があります。 このようなサブルーチンは、すべてコール側のスレッドの進行を妨害するか、または無限に計算を続けます。

明示的取り消しポイントは、pthread_testcancel サブルーチンのコールによっても作成することができます。 このサブルーチンは、単に取り消しポイントを作成するだけです。 据え置きの取り消し可能が有効な場合、 取り消し要求が保留になっていると、その要求が処理されて、スレッドは終了します。 それ以外の場合、サブルーチンはただ戻るだけです。

以下のサブルーチンを呼び出すと、他の取り消しポイントが発生します。

  • pthread_cond_wait
  • pthread_cond_timedwait
  • pthread_join

pthread_mutex_lock サブルーチンおよび pthread_mutex_trylock サブルーチンには、取り消しポイントがありません。 取り消しポイントがあると、これらのサブルーチンを呼び出すすべてのサブルーチン (および多くの関数) が、取り消しポイントを準備することになります。 取り消しポイントが多すぎると、 取り消し可能を無効にしたり、 復元したりする回数が多くなるため、 あるいはすべての可能な場所で信頼できるクリーンアップができるように調整を試みるために、 プログラミングが非常に困難になります。 これらのサブルーチンについて詳しくは、mutex の使用を参照してください。

取り消しポイントは、スレッドが以下の関数を実行しているときに発生します。

関数
aio_suspend close
creat fcntl
fsync getmsg
getpmsg lockf
mq_receive mq_send
msgrcv msgsnd
msync nanosleep
open pause
poll pread
pthread_cond_timedwait pthread_cond_wait
pthread_join pthread_testcancel
putpmsg pwrite
read readv
select sem_wait
sigpause sigsuspend
sigtimedwait sigwait
sigwaitinfo sleep
system tcdrain
usleep wait
wait3 waitid
waitpid write
writev

取り消しポイントは、スレッドが以下の関数を実行しているときにも発生する可能性があります。

関数 関数 関数
catclose catgets catopen
closedir closelog ctermid
dbm_close dbm_delete dbm_fetch
dbm_nextkey dbm_open dbm_store
dlclose dlopen endgrent
endpwent fwprintf fwrite
fwscanf getc getc_unlocked
getchar getchar_unlocked getcwd
getdate getgrent getgrgid
getgrgid_r getgrnam getgrnam_r
getlogin getlogin_r popen
printf putc putc_unlocked
putchar putchar_unlocked puts
pututxline putw putwc
putwchar readdir readdir_r
remove rename rewind
endutxent fclose fcntl
fflush fgetc fgetpos
fgets fgetwc fgetws
fopen fprintf fputc
fputs getpwent getpwnam
getpwnam_r getpwuid getpwuid_r
gets getutxent getutxid
getutxline getw getwc
getwchar getwd rewinddir
scanf seekdir semop
setgrent setpwent setutxent
strerror syslog tmpfile
tmpnam ttyname ttyname_r
fputwc fputws fread
freopen fscanf fseek
fseeko fsetpos ftell
ftello ftw glob
iconv_close iconv_open ioctl
lseek mkstemp nftw
opendir openlog pclose
perror ungetc ungetwc
unlink vfprintf vfwprintf
vprintf vwprintf wprintf
wscanf

関数の呼び出し中の中断時に取り消し要求を処理した場合の副次作用は、 単一スレッドのプログラムで、関数に対する呼び出しが、シグナルおよび一定の関数が [EINTR] を戻したために割り込まれたときに見られる副次作用と同じです。 何らかの取り消しクリーンアップ・ハンドラーを呼び出すまでは、 このような副次作用が起こります。

スレッドの取り消し可能が有効なときに、 そのスレッドをターゲットとして取り消し要求が出されていて、 スレッドが pthread_testcancel サブルーチンを呼び出す場合、その取り消し要求は pthread_testcancel サブルーチンが戻る前に処理されます。 スレッドの取り消し可能が有効で、 そのスレッドが保留中の非同期の取り消し要求を持っており、 スレッドが取り消しポイントでイベントの発生を待って中断されている場合、 取り消し要求は処理されます。 ただし、スレッドが取り消しポイントで中断されていて、 そのスレッドが待機しているイベントが取り消し要求の処理の前に発生する場合、取り消し要求が処理されるかどうか、 あるいは要求が保留のままになってスレッドが通常の実行を再開するかどうかは、イベントの順番により決まります。

取り消しの例

次の例では、両方の "writer" スレッドは、10 秒後、 およびメッセージを少なくとも 5 回書いた後で取り消されます。

#include <pthread.h>    /* include file for pthreads - the 1st */
#include <stdio.h>      /* include file for printf()           */
#include <unistd.h>     /* include file for sleep()            */

void *Thread(void *string)
{
        int i;
        int o_state;
 
        /* disables cancelability */
        pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &o_state);
 
        /* writes five messages */
        for (i=0; i<5; i++)
                printf("%s¥n", (char *)string);
 
        /* restores cancelability */
        pthread_setcancelstate(o_state, &o_state);
 
        /* writes further */
        while (1)
                printf("%s¥n", (char *)string);
        pthread_exit(NULL);
}
int main()
{
        char *e_str = "Hello!";
        char *f_str = "Bonjour !";
 
        pthread_t e_th;
        pthread_t f_th;
 
        int rc;
 
        /* creates both threads */
        rc = pthread_create(&e_th, NULL, Thread, (void *)e_str);
        if (rc)
                return -1;
        rc = pthread_create(&f_th, NULL, Thread, (void *)f_str);
        if (rc)
                return -1;
  
        /* sleeps a while */
        sleep(10);
 
        /* requests cancelation */
        pthread_cancel(e_th);
        pthread_cancel(f_th);
 
        /* sleeps a bit more */
        sleep(10);
        pthread_exit(NULL);
}

タイマー・サブルーチンとスリープ・サブルーチン

タイマー・ルーチンは、 コール側のスレッドに関係して実行されています。 そのため、タイマーが期限切れになった場合、 ウォッチドッグ・タイマー関数がそのスレッドに関係して呼び出されます。 プロセスまたはスレッドは、スリープに入るとプロセッサーを解放します。 マルチスレッド・プロセスでは、コール側のスレッドだけがスリープに入ります。

クリーンアップ・ハンドラーの使用

クリーンアップ・ハンドラーは、 スレッド終了時のリソースの解放とインバリアントの復元のための移植可能なメカニズムを提供します。

クリーンアップ・ハンドラーの呼び出し

クリーンアップ・ハンドラーは、スレッドごとに固有です。 スレッドは、複数のクリーンアップ・ハンドラーを持つことができ、 クリーンアップ・ハンドラーは、スレッド固有の LIFO (後入れ先出し) スタックに保管されます。 クリーンアップ・ハンドラーはすべて、次の場合に呼び出されます。

  • スレッドがそのエントリー・ポイント・ルーチンから戻る。
  • スレッドが pthread_exit サブルーチンを呼び出す。
  • スレッドが取り消し要求を処理する。

クリーンアップ・ハンドラーは、 pthread_cleanup_push サブルーチンによってクリーンアップ・スタック上にプッシュされます。 pthread_cleanup_pop サブルーチンは、 スタックの一番上のクリーンアップ・ハンドラーをスタックから出して、 オプションでそれを実行します。 このサブルーチンは、 クリーンアップ・ハンドラーが必要なくなったときに使用してください。

クリーンアップ・ハンドラーは、ユーザー定義ルーチンです。 これには、pthread_cleanup_push サブルーチンを呼び出すときに指定された 1 つのパラメーター (ボイド・ポインター) があります。 クリーンアップ・ハンドラーが操作を実行する必要がある一部のデータについては、 ポインターを指定することができます。

次の例では、ある操作を実行するためにバッファーが割り当てられます。 据え置きの取り消し可能が有効であるため、 任意の取り消しポイントで操作が停止する可能性があります。 その場合は、バッファーを解放するためにクリーンアップ・ハンドラーが設定されます。

/* the cleanup handler */
 
cleaner(void *buffer)
 
{
        free(buffer);
}

/* fragment of another routine */
...
myBuf = malloc(1000);
if (myBuf != NULL) {
        
        pthread_cleanup_push(cleaner, myBuf);
 
        /*
         *       perform any operation using the buffer,
         *       including calls to other functions
         *       and cancelation points
         */
        
        /* pops the handler and frees the buffer in one call */
        pthread_cleanup_pop(1);
}

据え置きの取り消し可能を使用すると、 バッファー割り当てとクリーンアップ・ハンドラーの登録の間で取り消し要求があっても、 スレッドはその要求を処理しません。 malloc サブルーチンも pthread_cleanup_push サブルーチンも 取り消しポイントをまったく提供しないためです。 クリーンアップ・ハンドラーをポップすると、 そのハンドラーが実行されて、バッファーを解放します。 さらに複雑なプログラムでは、ハンドラーをポップしたときに実行しない場合があります。これは、 クリーンアップ・ハンドラーが、 コードの保護部分のための「緊急出口」と見なされるからです。

プッシュ操作とポップ操作のバランス

pthread_cleanup_push サブルーチンと pthread_cleanup_pop サブルーチンは、常に同じ字句解析範囲内、 すなわち同じ関数内で同じステートメント・ブロック内で、対で使用する必要があります。 これらは、コードの保護部分を囲む左括弧と右括弧のようなものであると考えることができます。

この規則がある理由は、 一部のシステムでは、これらのサブルーチンがマクロとしてインプリメントされているためです。 pthread_cleanup_push サブルーチンは左括弧としてインプリメントされ、 次に、もう一方のステートメントが続きます。

#define pthread_cleanup_push(rtm,arg) { ¥
         /* other statements */
pthread_cleanup_pop サブルーチンは、 次のように他のステートメントに続く右括弧としてインプリメントされます。
#define pthread_cleanup_pop(ex) ¥
         /* other statements */  ¥
}

他のシステムに移植するときにプログラムのコンパイラー・エラーや 想定外の動作を避けるには、pthread_cleanup_push サブルーチンと pthread_cleanup_pop サブルーチンの バランスの規則に従ってください。

AIX® では、pthread_cleanup_push サブルーチンと pthread_cleanup_pop サブルーチンはライブラリー・ルーチンであるため、 同じステートメント・ブロック内で対になっていなくても構いません。 ただし、クリーンアップ・ハンドラーがスタックされるので、プログラム内では 対になっていなければなりません。

サブルーチン 説明
pthread_attr_destroy スレッドの属性オブジェクトを削除します。
pthread_attr_getdetachstate スレッドの属性オブジェクトの detachstate 属性の値を戻します。
pthread_attr_init スレッド属性オブジェクトを作成し、デフォルト値に初期化します。
pthread_cancel スレッドの取り消しを要求します。
pthread_cleanup_pop コール側のスレッドのクリーンアップ・スタックの先頭で、ルーチンを除去し、 オプショナルで実行します。
pthread_cleanup_push ルーチンをコール側のスレッドのクリーンアップ・スタックにプッシュします。
pthread_create 新規スレッドを作成して、その属性を初期化し、実行可能にします。
pthread_equal 2 つのスレッド ID を比較します。
pthread_exit コール側のスレッドを終了します。
pthread_self コール側のスレッドの ID を戻します。
pthread_setcancelstate コール側のスレッドの取り消し可能状態を設定します。
pthread_setcanceltype コール側のスレッドの取り消し可能タイプを設定します。
pthread_testcancel コール側のスレッドに取り消しポイントを作成します。