メインコンテンツ

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

複数のスレッドが同じ条件変数を待機しています

cnd_signal または std::condition_variable::notify_one() を使用して同じ条件変数を待機しているいずれかのスレッドに通知すると、無期限のブロッキングにつながる可能性がある

説明

このチェッカーは、既定の Polyspace® as You Code 解析では非アクティブにされますPolyspace as You Code 解析で非アクティブにされるチェッカー (Polyspace Access)を参照してください

この欠陥は、cnd_signal ファミリ関数または関数 std::condition_variable::notify_one() を使用して、同じ条件変数を同時に待機している少なくとも 2 つのスレッドのいずれかに通知した場合に発生します。優先順位が同じスレッドの場合は、これらの関数により、条件変数を待機しているいずれかのスレッドをスレッド スケジューラが自動で選択し通知します。

Polyspace は関数呼び出しでこのチェックを報告します。同じ条件変数を待機しているスレッドを確認するには、[結果の詳細] ペインで [イベント] 列を参照してください。

リスク

複数のスレッドで同じ条件変数を使用している場合は、cnd_signal ファミリ関数または関数 std::condition_variable::notify_one() が、待機しているいずれかのスレッドを任意に選んで通知します。通知されたスレッドは、通常、条件述部を検査します。条件述部が false の場合は、再度通知されるまでスレッドは待機し続けます。このメソッドは任意のスレッドを選んで通知するので、通知されるスレッドの条件術部が true にならない可能性があります。プログラムは、条件変数を通知するスレッドがいずれも起動していない状態となり、無期限のブロッキングにつながる可能性があります。

修正方法

cnd_broadcast ファミリ関数または関数 std::condition_variable::notify_all() を使用して、条件変数を待機しているすべてのスレッドに通知するか、スレッドごとに別々の条件変数を使用します。

すべて展開する

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <threads.h>

typedef int thrd_return_t;

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

enum { NTHREADS = 5 };

mtx_t mutex;
cnd_t cond;

thrd_return_t next_step(void* t)
{
    static size_t current_step = 0;
    size_t my_step = *(size_t*)t;

    if (thrd_success != mtx_lock(&mutex)) {
        /* Handle error */
        fatal_error();
    }

    printf("Thread %zu has the lock\n", my_step);
    while (current_step != my_step) {
        printf("Thread %zu is sleeping...\n", my_step);
        if (thrd_success !=
            cnd_wait(&cond, &mutex)) {
            /* Handle error */
            fatal_error();
        }
        printf("Thread %zu woke up\n", my_step);
    }
    /* Do processing ... */
    printf("Thread %zu is processing...\n", my_step);
    current_step++;

    /* Signal a waiting task */
    if (thrd_success !=
        cnd_signal(&cond)) {
        /* Handle error */
        fatal_error();
    }

    printf("Thread %zu is exiting...\n", my_step);

    if (thrd_success != mtx_unlock(&mutex)) {
        /* Handle error */
        fatal_error();
    }
    return (thrd_return_t)0;
}

int main(void)
{
    thrd_t threads[NTHREADS];
    size_t step[NTHREADS];

    if (thrd_success != mtx_init(&mutex, mtx_plain)) {
        /* Handle error */
        fatal_error();
    }
    if (thrd_success != cnd_init(&cond)) {
        /* Handle error */
        fatal_error();
    }
    /* Create threads */
    for (size_t i = 0; i < NTHREADS; ++i) {
        step[i] = i;
        if (thrd_success != thrd_create(&threads[i],
                                        next_step,
                                        &step[i])) {
            /* Handle error */
            fatal_error();
        }
    }
    /* Wait for all threads to complete */
    for (size_t i = NTHREADS; i != 0; --i) {
        if (thrd_success != thrd_join(threads[i - 1], NULL)) {
            /* Handle error */
            fatal_error();
        }
    }
    (void)mtx_destroy(&mutex);
    (void)cnd_destroy(&cond);
    return 0;
}

この例では、複数のスレッドが作成され、ステップ レベルが割り当てられます。各スレッドは、割り当てられたステップ レベルが現在のステップ レベル (条件述部) と一致するかどうかをチェックします。述部が false の場合は、スレッドが条件変数 cond 上での待機に戻ります。cond を通知するために cnd_signal を使用すると、スレッド スケジューラが cond 上で待機しているスレッドのいずれかを任意に起動します。これにより、起動されたスレッドの条件述部が false で、他のどのスレッドも cond の通知に使用できない場合に無期限のブロッキングが発生する可能性があります。

修正 — すべてのスレッドを起動するために cnd_broadcast を使用

1 つの修正方法として、代わりに cnd_broadcast を使用して cond を通知します。関数 cnd_signal は、cond 上で待機しているすべてのスレッドを起動します。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <threads.h>

typedef int thrd_return_t;

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

enum { NTHREADS = 5 };

mtx_t mutex;
cnd_t cond;

thrd_return_t next_step(void* t)
{
    static size_t current_step = 0;
    size_t my_step = *(size_t*)t;

    if (thrd_success != mtx_lock(&mutex)) {
        /* Handle error */
        fatal_error();
    }

    printf("Thread %zu has the lock\n", my_step);
    while (current_step != my_step) {
        printf("Thread %zu is sleeping...\n", my_step);
        if (thrd_success !=
            cnd_wait(&cond, &mutex)) {
            /* Handle error */
            fatal_error();
        }
        printf("Thread %zu woke up\n", my_step);
    }
    /* Do processing ... */
    printf("Thread %zu is processing...\n", my_step);
    current_step++;

    /* Signal a waiting task */
    if (thrd_success !=
        cnd_broadcast(&cond)) {
        /* Handle error */
        fatal_error();
    }

    printf("Thread %zu is exiting...\n", my_step);

    if (thrd_success != mtx_unlock(&mutex)) {
        /* Handle error */
        fatal_error();
    }
    return (thrd_return_t)0;
}

int main_test_next_step(void)
{
    thrd_t threads[NTHREADS];
    size_t step[NTHREADS];

    if (thrd_success != mtx_init(&mutex, mtx_plain)) {
        /* Handle error */
        fatal_error();
    }
    if (thrd_success != cnd_init(&cond)) {
        /* Handle error */
        fatal_error();
    }
    /* Create threads */
    for (size_t i = 0; i < NTHREADS; ++i) {
        step[i] = i;
        if (thrd_success != thrd_create(&threads[i],
                                        next_step,
                                        &step[i])) {
            /* Handle error */
            fatal_error();
        }
    }
    /* Wait for all threads to complete */
    for (size_t i = NTHREADS; i != 0; --i) {
        if (thrd_success != thrd_join(threads[i - 1], NULL)) {
            /* Handle error */
            fatal_error();
        }
    }
    (void)mtx_destroy(&mutex);
    (void)cnd_destroy(&cond);
    return 0;
}

この例では、複数のスレッドが作成され、ステップ番号が割り当てられます。これらのすべてのスレッドは同じ std::condition_variable conVar を使用して制御されます。各スレッドは、割り当てられたステップ番号が現在のステップ番号と一致するかどうかをチェックします。スレッドに割り当てられたステップ番号が現在のステップ番号と一致しない場合、そのスレッドは条件変数の待機に戻ります。コードは std::condition_variable::notify_one() を使用して conVar に通知するので、任意に選択されて通知されるスレッドに currentStep とは異なる stepNumber がある可能性があり、その場合には通知されたスレッドは待機に戻ります。他のどのスレッドも起動していない場合は、conVar が再度通知を受けることはなく、無期限のブロッキングが発生します。

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
  
std::mutex myMutex;
std::condition_variable conVar;
 
void stepFunc(size_t stepNumber) {
  static size_t currentStep = 0;
  std::unique_lock<std::mutex> uLock(myMutex);
 
 
  while (currentStep != stepNumber) {
    conVar.wait(uLock);
  }
 
  // Do processing...
  //....
  currentStep++;
 
  // Signal awaiting task.
  conVar.notify_one(); 
 
}
 
void foo() {
  constexpr size_t nThreads = 5;
  std::thread threads[nThreads];
 
  // Create threads.
  for (size_t i = 0; i < nThreads; ++i) {
    threads[i] = std::thread(stepFunc, i);
  }
 
  // Wait for all threads to complete.
  for (size_t i = nThreads; i != 0; --i) {
    threads[i - 1].join();
  }
}
修正 — std::condition_variable::notify_all() を使用してすべてのスレッドに通知

1 つの修正方法として、代わりに std::condition_variable::notify_all() を使用して conVar を通知します。関数 std::condition_variable::notify_all() は、conVar を待機しているすべてのスレッドに通知します。少なくとも 1 つのスレッドは (currentStep != stepNumber) のテストに失敗し、currentStep がインクリメントされます。

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
  
std::mutex myMutex;
std::condition_variable conVar;
 
void stepFunc(size_t stepNumber) {
  static size_t currentStep = 0;
  std::unique_lock<std::mutex> uLock(myMutex);
 
 
  while (currentStep != stepNumber) {
    conVar.wait(uLock);
  }
 
  // Do processing...
  //....
  currentStep++;
 
  // Signal ALL awaiting task.
  conVar.notify_all(); //Compliant
 
}
 
void foo() {
  constexpr size_t nThreads = 5;
  std::thread threads[nThreads];
 
  // Create threads.
  for (size_t i = 0; i < nThreads; ++i) {
    threads[i] = std::thread(stepFunc, i);
  }
 
  // Wait for all threads to complete.
  for (size_t i = nThreads; i != 0; --i) {
    threads[i - 1].join();
  }
}
修正 — スレッドごとに別々の条件変数を使用

std::condition_variable::notify_all() を使用してすべてのスレッドに通知する方法は、いつでもスレッドセーフとは限りません。代わりの方法として、スレッドごとに別々の条件変数を使用する方法があります。この方法では、スレッドは通知するスレッドを選択できます。このコードでは、各スレッドは次の stepNumber に対応するスレッドに通知します。通知されたスレッドは待機に戻らず、currentStep はインクリメントされます。

#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
  constexpr size_t numThreads = 5;
std::mutex myMutex;
std::condition_variable conVar[numThreads];
 
void stepFunc(size_t stepNumber) {
  static size_t currentStep = 0;
  std::unique_lock<std::mutex> uLock(myMutex);
 
 
  while (currentStep != stepNumber) {
    conVar[stepNumber].wait(uLock);
  }
 
  // Do processing...
  //....
  currentStep++;
 
  // Signal awaiting task.
  if ((stepNumber + 1) < numThreads) {
    conVar[stepNumber + 1].notify_one();
  } //Noncompliant
 
}
 
void foo() {
  constexpr size_t nThreads = 5;
  std::thread threads[nThreads];
 
  // Create threads.
  for (size_t i = 0; i < nThreads; ++i) {
    threads[i] = std::thread(stepFunc, i);
  }
 
  // Wait for all threads to complete.
  for (size_t i = nThreads; i != 0; --i) {
    threads[i - 1].join();
  }
}

結果情報

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

バージョン履歴

R2020a で導入