メインコンテンツ

CERT C++: CON55-CPP

条件変数の使用時にスレッドの安全性と存続性を維持する

R2023b 以降

説明

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

ルール定義

条件変数の使用時にスレッドの安全性と存続性を維持します。1

Polyspace 実装

ルール チェッカーは、問題 "複数のスレッドが同じ条件変数を待機しています" をチェックします。

すべて展開する

問題

この問題は、std::condition_variable::notify_one() 関数を使用して、同じ条件変数を同時に待機している少なくとも 2 つのスレッドのいずれかに通知する場合に発生します。優先順位が同じスレッドの場合は、この関数により、スレッド スケジューラが、条件変数を待機しているいずれかのスレッドを任意に選んで通知します。

同じ条件変数を待機しているスレッドを確認するには、[結果の詳細] ペインで [イベント] 列を参照してください。

リスク

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

修正方法

この問題を修正するには、以下のいずれかを行います。

  • スレッドごとに別々の条件変数を使用する。

  • std::condition_variable::notify_all() 関数を使用して、条件変数を待機しているすべてのスレッドに通知する。

例 - std::condition_variable::notify_one() を使用して、同じ条件変数を待機している複数のスレッドのいずれかに通知する

この例では、複数のスレッドが作成され、ステップ番号が割り当てられます。これらのすべてのスレッドは同じ 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(); //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();
  }
}
修正 — 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();
  }
 
}
 
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();
  }
}

チェック情報

グループ: 10.同時実行 (CON)

バージョン履歴

R2023b で導入


1 This software has been created by MathWorks incorporating portions of: the “SEI CERT-C Website,” © 2017 Carnegie Mellon University, the SEI CERT-C++ Web site © 2017 Carnegie Mellon University, ”SEI CERT C Coding Standard – Rules for Developing safe, Reliable and Secure systems – 2016 Edition,” © 2016 Carnegie Mellon University, and “SEI CERT C++ Coding Standard – Rules for Developing safe, Reliable and Secure systems in C++ – 2016 Edition” © 2016 Carnegie Mellon University, with special permission from its Software Engineering Institute.

ANY MATERIAL OF CARNEGIE MELLON UNIVERSITY AND/OR ITS SOFTWARE ENGINEERING INSTITUTE CONTAINED HEREIN IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.

This software and associated documentation has not been reviewed nor is it endorsed by Carnegie Mellon University or its Software Engineering Institute.