Main Content

CERT C++: CON55-CPP

Preserve thread safety and liveness when using condition variables

Since R2023b

Description

This checker is deactivated in a default Polyspace® as You Code analysis. See Checkers Deactivated in Polyspace as You Code Analysis (Polyspace Access).

Rule Definition

Preserve thread safety and liveness when using condition variables.1

Polyspace Implementation

The rule checker checks for the issue Multiple threads waiting for same condition variable.

Examples

expand all

Issue

This issue occurs when you use the std::condition_variable::notify_one() function to notify one of at least two threads that are concurrently waiting for the same condition variable. For threads with the same priority level, this function causes the thread scheduler to arbitrarily notify one of the threads waiting for the condition variable.

See the Event column in the Results Details pane to view the threads waiting for the same condition variable.

Risk

When multiple threads use the same std::condition_variable, then the function std::condition_variable::notify_one() arbitrarily notifies one of the waiting threads. The notified thread usually tests for a condition predicate. If the condition predicate is false, the thread continues to wait until it is notified again. Because this method notifies an arbitrary thread, it is possible that the condition predicate for the notified thread never becomes true. The program ends up in a state where no thread is awake, resulting in indefinite blocking.

Fix

To fix this issue, do one of the following:

  • Use unique condition variable for each thread.

  • Use the std::condition_variable::notify_all() function to notify all threads waiting on the condition variable.

Example - Use of std::condition_variable::notify_one() to Notify One of Many Threads Waiting on Same Condition Variable

In this example, multiple threads are created and assigned step numbers. All these threads are controlled using the same std::condition_variable conVar. Each thread checks if its assigned step number matches the current step number. If the assigned step number of a thread does not match the current step number, the thread goes back to waiting on the condition variable. Because the code uses std::condition_variable::notify_one() to notify conVar, it is possible that the arbitrarily notified thread has a different stepNumber than the currentStep, which causes the notified thread to resume waiting. If no other thread is awake, then conVar is never notified again, resulting in indefinite blocking.

#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();
  }
}
Correction — Use std::condition_variable::notify_all() to Notify All Threads

One possible correction is to use std::condition_variable::notify_all() instead to signal conVar. The function std::condition_variable::notify_all() notifies all the thread that are waiting on conVar. At least one of the threads fail the test (currentStep != stepNumber) and currentStep is incremented.

#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();
  }
}
Correction — Use Unique Condition Variable For Each Thread

Notifying all threads by using std::condition_variable::notify_all() is not always thread-safe. An alternative is to use unique condition variables for each thread. This way, a thread can choose which thread to notify. In this code, each thread signals the thread corresponding to the next stepNumber. The notified threads does not resume waiting and currentStep is incremented.

#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();
  }
}

Check Information

Group: 10. Concurrency (CON)

Version History

Introduced in 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.