Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Updated and added a second compliant solution

...

Mutexes are used to protect shared data structures being concurrently accessed. If a mutex is destroyed while a thread is blocked waiting for that mutexcritical sections (shared data that would otherwise be protected from data races) are no longer protected.

The C++ Standard, 7 [thread.26.4.1mutex.class], paragraph 2 5 [ISO/IEC 9899:201114882-2014], states:

The mtx_destroy function releases any resources used by the mutex pointed to by mtx. No threads can be blocked waiting for the mutex pointed to by mtx.behavior of a program is undefined if it destroys a mutex object owned by any thread or a thread terminates while owning a mutex object.

This statement implies that destroying a mutex while a thread is waiting on it is undefined behavior.

...

This noncompliant code example creates several threads that each invoke the do_work() function, passing a unique number as an ID. The do_work() function initializes the lock mutex if the argument is 0 and destroys the mutex if the argument is max_threads - 1. In all other cases, the do_work() function provides normal processing. Each thread, except the final cleanup thread, increments the atomic completed variable when it is finished.

Code Block
bgColor#ffcccc
langc
#include <stdatomic.h><mutex>
#include <threads.h><thread>
 
mtx_t lock;
/* Atomic so multiple threads can modify safely */
atomic_int completed = ATOMIC_VAR_INIT(0);
enum { const size_t max_threads = 5 }10; 

intvoid do_work(voidsize_t i, std::mutex *arglockp) 
{
  int *i = (int *)arg;

  if (*i == 0) { /* Creation thread */
    if (thrd_success != mtx_init(&lock, mtx_plain)) {
      /* Handle error */
    }
    atomic_store(&completed, 1);
  } else if (*std::lock_guard<std::mutex> guard(*lockp);

  // Access data protected by the lock.
}

void start_threads(void)
{
  std::thread threads[max_threads];
  std::mutex lock;

  for (size_t i = 0; i < max_threads - 1; ++i) {
 /* Worker thread */
    if (thrd_success != mtx_lock(&lock)) {
      /* Handle error */
    }
    /* Access data protected by the lock */
    atomic_fetch_add(&completed, 1);
    if (thrd_success != mtx_unlock(&lock)) {
      /* Handle error */
    }
  } else { /* Destruction thread */
    mtx_destroy(&lock);
  }
  return 0;
}
 
int main(void) {
  thrd_t threads[i] = std::thread(do_work, i, &lock);
  }
}

Compliant Solution

This compliant solution eliminates the race condition by extending the lifetime of the lock:

Code Block
bgColor#ccccff
langc
#include <mutex>
#include <thread>

const size_t max_threads = 10;

void do_work(size_t i, std::mutex *lockp)
{
  std::lock_guard<std::mutex> guard(*lockp);

  // Access data protected by the lock.
}

std::mutex lock;

void start_threads(void)
{
  std::thread threads[max_threads];
  
  for (size_t i = 0; i < max_threads; i++i) {
    if (thrd_success != thrd_create(&threads[i],  = std::thread(do_work, &i)) {
      /* Handle error */
    }
  }
  for (size_t i = 0; i < max_threads; i++) {
    if (thrd_success != thrd_join(threads[i], 0)) {
      /* Handle error */
    }
  }
  return 0;, &lock);
  }
}

Compliant Solution

This compliant solution eliminates the race conditions condition by initializing the mutex in main() before creating the threads and by destroying the mutex in main() after joining the threads:joining the threads before the lock's destructor is invoked:

 

Code Block
bgColor#ccccff
langc
#include <stdatomic.h><mutex>
#include <threads.h><thread>
 
mtx_t lock;
/* Atomic so multiple threads can increment safely */
atomic_int completed = ATOMIC_VAR_INIT(0);
enum { const size_t max_threads = 5 }10; 

intvoid do_work(voidsize_t i, std::mutex *dummylockp) 
{
  if (thrd_success != mtx_lock(&lock)) {
    /* Handle error */
  }
  /*std::lock_guard<std::mutex> guard(*lockp);

  // Access data protected by the lock */
  atomic_fetch_add(&completed, 1);
  if (thrd_success != mtx_unlock(&lock)) {
    /* Handle error */
  }

  return 0;
}

int main(void) {
  thrd_t.
}
void run_threads(void)
{
  std::thread threads[max_threads];
  
  if (thrd_success != mtx_init(&lock, mtx_plain)) {
    /* Handle error */
  }std::mutex lock;

  for (size_t i = 0; i < max_threads; i++i) {
    if (thrd_success != thrd_create(&threads[i],  = std::thread(do_work, i, NULL)) {
      /* Handle error */
    }
  }&lock);
  }

  for (size_t i = 0; i < max_threads; i++i) {
    if (thrd_success != thrd_join(threads[i], 0)) {
      /* Handle error */
    }
  }

  mtx_destroy(&lock.join();
  return 0;}
}


Risk Assessment

Destroying a mutex while it is locked may result in invalid control flow and data corruption.

...