PTHREAD_CLEANUP_PUSH(3)

名前
pthread_cleanup_push, pthread_cleanup_pop - スレッドの キャンセルクリーンアップハンドラーの push/pop を行う

書式
#include <pthread.h>

void pthread_cleanup_push(void (*routine)(void *),
                          void *arg);
void pthread_cleanup_pop(int execute);

-pthread でコンパイルしてリンクする。

説明
これらの関数は、呼び出したスレッドのスレッドキャンセル時のクリーンアッ プハンドラーのスタックの操作を行う。クリーンアップハンドラーは、スレッドが キャンセルされた場合 (や以下で説明する他の種々の状況において) 自動的に 実行される関数である。例えば、mutex のロック解除を行い、プロセス内の 他のスレッドが利用できるようにする関数などが考えられる。

pthread_cleanup_push() 関数は、 routine をクリーンアップ ハンドラーのスタックの一番上にプッシュする。 routine が後で 起動される際には、 arg が関数の引数と渡される。

pthread_cleanup_pop() 関数は、クリーンアップハンドラーの スタックの一番上のルーチンを削除する。 execute が 0 以外の場合にはそのルーチンを追加で実行する。

キャンセルクリーンアップハンドラーは、以下に示す場合に スタックから取り出され実行される。

1.  スレッドがキャンセルされた際に、スタックに登録された全てのクリーン アップハンドラーが取り出されて、実行される。クリーンアップハンドラーの 実行は、スタックに登録されたのと逆の順序で行われる。

2.  スレッドが pthread_exit(3) を呼び出して終了する際に、全てのクリーン アップハンドラーが上の項目で述べたのと同様に実行される。 (スレッドがスレッド開始関数からの return の実行により終了する場合に は、クリーンアップハンドラーは呼び出されない。)

3.  スレッドが 0 以外の execute 引数で pthread_cleanup_pop() を 呼び出した際に、スタックの一番上のクリーンアップハンドラーが取り出されて 実行される。

POSIX.1 では、 pthread_cleanup_push() と pthread_cleanup_pop() を それぞれ '{' と '}' を含むテキストに展開するマクロと して実装することを許容している。 このため、呼び出し側では、これらの関数の呼び出しが同じ関数の中で対と なり、かつ文法的に同じネストレベル (nesting level) になることを保証 しなければならない。 (言い換えると、クリーンアップハンドラーは、コード の特定のセクションの実行の中でのみ設定するものであると言える。)

longjmp(3) (siglongjmp(3)) の呼び出しは、 pthread_cleanup_push() や pthread_cleanup_pop() の呼び出しが対と なる呼び出しがない状態で行われた場合には、どのような結果になるかは不定 である。これは jump バッファーは setjmp(3) (sigsetjmp(3)) により設 定されるからである。同様に、クリーンアップハンドラー内からの longjmp(3) (siglongjmp(3)) の呼び出しも、jump バッファーがハンドラー 内で setjmp(3) (sigsetjmp(3)) で設定されていない限り、どのような 結果になるかは不定である。

返り値
これらの関数は値を返さない。

エラー
エラーはない。

属性
この節で使用されている用語の説明については、 attributes(7) を参照。
インターフェース | 属性 | 値
pthread_cleanup_push(), pthread_cleanup_pop() | Thread safety | MT-Safe

準拠
POSIX.1-2001, POSIX.1-2008.

注意
Linux では、関数 pthread_cleanup_push() と pthread_cleanup_pop() は、それぞれ '{' と '}' を含むテキストに展開する マクロとして実装されている。このことは、これらの関数を対で呼び出した スコープ内で宣言された変数は、そのスコープの中でしか参照できない ということを意味している。

POSIX.1 には、括弧を含む pthread_cleanup_push() と pthread_cleanup_pop() のブロックをそのままにしたままで、 return, break, continue, goto を使った場合の影響は 不定であると書かれている。 移植性が必要なアプリケーションではこれを行うのは避けるべきである。

例
以下のプログラムは、このページで説明した関数の簡単な使用例を示すもので ある。このプログラムは pthread_cleanup_push() と pthread_cleanup_pop() で囲まれたループを実行するスレッドを作成する。 このループではグローバル変数 cnt を 1 秒に 1 ずつ増やしていく。 指定されたコマンドライン引数の内容に基づいて、メインスレッドはもう一 つのスレッドにキャンセル要求を送ったり、もう一つのスレッドがループを 抜けて (return を呼び出して) 正常終了するようにグローバル変数を 設定したりする。

以下のシェルセッションでは、メインスレッドはもう一つのスレッドに キャンセル要求を送信する。

    $ ./a.out
    New thread started
    cnt = 0
    cnt = 1
    Canceling thread
    Called clean-up handler
    Thread was canceled; cnt = 0

上記の実行例から、スレッドがキャンセルされ、 キャンセルクリーンアップハンドラーが呼び出され、 グローバル変数 cnt の値が 0 にリセットされていることが確認できる。

次の実行例では、メインプログラムはグローバル変数を設定して、 もう一つのスレッドが正常終了するようにしている。

    $ ./a.out x
    New thread started
    cnt = 0
    cnt = 1
    Thread terminated normally; cnt = 2

上記では、 (cleanup_pop_arg が 0 なので) クリーンアップハンドラーは 実行されておらず、その結果 cnt の値はリセットされていないことが 分かる。

次の実行例では、メインプログラムはグローバル変数を設定して、 もう一つのスレッドが正常終了するようにし、さらに cleanup_pop_arg に 0 以外の値を渡している。

    $ ./a.out x 1
    New thread started
    cnt = 0
    cnt = 1
    Called clean-up handler
    Thread terminated normally; cnt = 0

上記では、スレッドはキャンセルされていないが、クリーンアップハンドラーが実行されたことが分かる。これは pthread_cleanup_pop() の引数に 0 以外を渡したからである。

  プログラムのソース
    #include <pthread.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #define handle_error_en(en, msg) \
            do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
    static int done = 0;
    static int cleanup_pop_arg = 0;
    static int cnt = 0;
    static void
    cleanup_handler(void *arg)
    {
        printf("Called clean-up handler\n");
        cnt = 0;
    }
    static void *
    thread_start(void *arg)
    {
        time_t start, curr;
        printf("New thread started\n");
        pthread_cleanup_push(cleanup_handler, NULL);
        curr = start = time(NULL);
        while (!done) {
            pthread_testcancel();           /* A cancellation point */
            if (curr < time(NULL)) {
                curr = time(NULL);
                printf("cnt = %d\n", cnt);  /* A cancellation point */
                cnt++;
            }
        }
        pthread_cleanup_pop(cleanup_pop_arg);
        return NULL;
    }
    int
    main(int argc, char *argv[])
    {
        pthread_t thr;
        int s;
        void *res;
        s = pthread_create(&thr, NULL, thread_start, NULL);
        if (s != 0)
            handle_error_en(s, "pthread_create");
        sleep(2);           /* Allow new thread to run a while */
        if (argc > 1) {
            if (argc > 2)
                cleanup_pop_arg = atoi(argv[2]);
            done = 1;
        } else {
            printf("Canceling thread\n");
            s = pthread_cancel(thr);
            if (s != 0)
                handle_error_en(s, "pthread_cancel");
        }
        s = pthread_join(thr, &res);
        if (s != 0)
            handle_error_en(s, "pthread_join");
        if (res == PTHREAD_CANCELED)
            printf("Thread was canceled; cnt = %d\n", cnt);
        else
            printf("Thread terminated normally; cnt = %d\n", cnt);
        exit(EXIT_SUCCESS);
    }

関連項目
pthread_cancel(3), pthread_cleanup_push_defer_np(3), pthread_setcancelstate(3), pthread_testcancel(3), pthreads(7)

この文書について
この man ページは Linux man-pages プロジェクトのリリース 5.10 の一部である。プロジェクトの説明とバグ報告に関する情報は https://www.kernel.org/doc/man-pages/ に書かれている。