メインコンテンツ

このページの内容は最新ではありません。最新版の英語を参照するには、ここをクリックします。

非同期にキャンセル可能なスレッド

呼び出し元スレッドが安全ではない状態でキャンセルされる可能性がある

説明

この欠陥は、pthread_setcanceltype を引数 PTHREAD_CANCEL_ASYNCHRONOUS と一緒に使用して、呼び出し元スレッドのキャンセル可能性タイプを非同期 (または即時) に設定した場合に発生します。非同期でキャンセル可能なスレッドは、いつでも、通常は、キャンセル要求の受信直後にキャンセルできます。

リスク

呼び出し元スレッドが、リソース リーク、デッドロック、データ レース、データ破損、または予測できない動作につながる安全ではない状態でキャンセルされる可能性があります。

修正方法

引数 PTHREAD_CANCEL_ASYNCHRONOUS を指定した pthread_setcanceltype の呼び出しを削除して、代わりに、既定のキャンセル可能性タイプ PTHREAD_CANCEL_DEFERRED を使用します。既定のキャンセル可能性タイプを使用して、スレッドは、キャンセル ポイントである関数を呼び出すまでキャンセル要求を延期します。

すべて展開する

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

static int fatal_error(void)
{
    exit(1);
}


volatile int a = 5;
volatile int b = 10;

pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;

void* swap_values_thread(void* dummy)
{
    int i;
    int c;
    int result;
    if ((result =
             pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &i)) != 0) {
        /* handle error */
        fatal_error();
    }
    while (1) {
        if ((result = pthread_mutex_lock(&global_lock)) != 0) {
            /* handle error */
            fatal_error();
        }
        c = b;
        b = a;
        a = c;
        if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
            /* handle error */
            fatal_error();
        }
    }
    return NULL;
}

int main(void)
{
    int result;
    pthread_t worker;

    if ((result = pthread_create(&worker, NULL, swap_values_thread, NULL)) != 0) {
        /* handle error */
        fatal_error();
    }

    /* Additional code */

    if ((result = pthread_cancel(worker)) != 0) {
        /* handle error */
        fatal_error();
    }


    if ((result = pthread_join(worker, 0)) != 0) {
        /* handle error */
        fatal_error();
    }

    if ((result = pthread_mutex_lock(&global_lock)) != 0) {
        /* handle error */
        fatal_error();
    }
    printf("a: %i | b: %i", a, b);
    if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
        /* handle error */
        fatal_error();
    }

    return 0;
}

この例では、worker スレッドのキャンセル可能性タイプが非同期に設定されます。ミューテックス global_lock によって、worker スレッドと main スレッドがアクセス変数の ab に同時にアクセスしないようになります。ただし、worker スレッドが global_lock の保持中にキャンセルされる可能性があり、その場合は、main スレッドが global_lock を取得することはなく、デッドロックが発生します。

修正 — 既定のキャンセル可能性タイプを使用

1 つの修正方法として、pthread_setcanceltype に対する呼び出しを削除します。既定で、新しいスレッドのキャンセル可能性タイプは PTHREAD_CANCEL_DEFERRED に設定されます。worker スレッドは、キャンセル ポイントである関数を呼び出すまでキャンセル要求を延期します。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

static int fatal_error(void)
{
    exit(1);
}


volatile int a = 5;
volatile int b = 10;

pthread_mutex_t global_lock = PTHREAD_MUTEX_INITIALIZER;

void* swap_values_thread(void* dummy)
{
    int i;
    int c;
    int result;
    while (1) {
        if ((result = pthread_mutex_lock(&global_lock)) != 0) {
            /* handle error */
            fatal_error();
        }
        c = b;
        b = a;
        a = c;
        if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
            /* handle error */
            fatal_error();
        }
    }
    return NULL;
}

int main(void)
{
    int result;
    pthread_t worker;

    if ((result = pthread_create(&worker, NULL, swap_values_thread, NULL)) != 0) {
        /* handle error */
        fatal_error();
    }

    /* Additional code */

    if ((result = pthread_cancel(worker)) != 0) {
        /* handle error */
        fatal_error();
    }


    if ((result = pthread_join(worker, 0)) != 0) {
        /* handle error */
        fatal_error();
    }

    if ((result = pthread_mutex_lock(&global_lock)) != 0) {
        /* handle error */
        fatal_error();
    }
    printf("a: %i | b: %i", a, b);
    if ((result = pthread_mutex_unlock(&global_lock)) != 0) {
        /* handle error */
        fatal_error();
    }

    return 0;
}

結果情報

グループ: 同時実行
言語: C | C++
既定値: オフ
コマンド ライン構文: ASYNCHRONOUSLY_CANCELLABLE_THREAD
影響度: Medium

バージョン履歴

R2020a で導入