Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

This compliant solution uses the cnd_broadcast() method to signal all waiting threads instead of a single " random " one.
Only  Only the run_step() thread code from the noncompliant code example is modified, as follows:

Code Block
bgColor#ccccff
langc
void *run_step(void *t) {
  static int current_step = 0;
  int my_step = (int)t;
  int result;

  if ((result = mtx_lock(&mutex)) != thrd_success) {
    /* Handle error condition */
  }

  printf("Thread %d has the lock\n", my_step);

  while (current_step != my_step) {
    printf("Thread %d is sleeping...\n", my_step);

    if ((result = cnd_wait(&cond, &mutex)) != thrd_success) {
      /* Handle error condition */
    }

    printf("Thread %d woke up\n", my_step);
  }

  /* Do processing... */
  printf("Thread %d is processing...\n", my_step);

  current_step++;

  /* Signal ALL waiting tasks */
  if ((result = cnd_broadcast(&cond)) != thrd_success) {
    /* Handle error condition */
  }

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

  if ((result = mtx_unlock(&mutex)) != 0) {
    /* Handle error condition */
  }

  thrd_exit(NULL);
}

The fact that all tasks threads will be awake solves the problem because all tasks end because each one ends up executing its predicate test; therefore, one will find it to its test to be true and will continue the execution until the end.

Compliant Solution (Using cnd_signal() but with a Unique Condition Variable

...

per Thread)

Another way to solve the signal issue is to use a unique condition variable for each thread (maintaining a single mutex associated with it). In this case, the signal operation (cnd_signal()) will wake wakes up the only thread waiting on it. 

NOTE: The predicate of the signaled thread must be true; otherwise, a deadlock can occur anyway.

Code Block
bgColor#ccccff
langc
#include <stdio.h>
#include <stdlib.h>
#include <threads.h>

#define NTHREADS  5
mtx_t mutex;
cnd_t cond[NTHREADS];


void *run_step(void *t) {
  static int current_step = 0;
  int my_step = (int)t;
  int result;

  if ((result = mtx_lock(&mutex)) != thrd_success) {
    /* Handle error condition */
  }

  printf("Thread %d has the lock\n", my_step);

  while (current_step != my_step) {
    printf("Thread %d is sleeping...\n", my_step);

    if ((result = cnd_wait(&cond[my_step], &mutex)) != thrd_success) {
      /* Handle error condition */
    }

    printf("Thread %d woke up\n", my_step);
  }

  /* Do processing... */
  printf("Thread %d is processing...\n", my_step);

  current_step++;

  /* Signal next step thread */
  if ((my_step + 1) < NTHREADS) {
    if ((result = cnd_signal(&cond[my_step+1])) != thrd_success) {
      /* Handle error condition */
    }
  }

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

  if ((result = mtx_unlock(&mutex)) != thrd_success) {
    /* Handle error condition */
  }

  thrd_exit(NULL);
}


int main(int argc, char** argv) {
  int i;
  int result;
  thrd_t threads[NTHREADS];
  int step[NTHREADS];

  if ((result = mtx_init(&mutex, mtx_plain)) != thrd_success) {
    /* Handle error condition */
  }

  for (i = 0; i< NTHREADS; i++) {
    if ((result = cnd_init(&cond[i])) != thrd_success) {
      /* Handle error condition */
    }
  }

  /* Create threads */
  for (i = 0; i < NTHREADS; i++) {
    step[i] = i;
    if ((result = thrd_create(&threads[i], run_step, (void *)step[i])) != thrd_success) {
      /* Handle error condition */
    }
  }

  /* Wait for all threads to complete */
  for (i = NTHREADS-1; i >= 0; i--) {
    if ((result = thrd_join(threads[i], NULL)) != thrd_success) {
      /* Handle error condition */
    }
  }

  if ((result = mtx_destroy(&mutex)) != thrd_success) {
    /* Handle error condition */
  }

  for (i = 0; i < NTHREADS; i++) {
    if ((result = cnd_destroy(&cond[i])) != thrd_success) {
      /* Handle error condition */
    }
  }

  thrd_exit(NULL);
}

In the above codeIn this compliant code, each thread has associated a unique condition variable which that is signaled when that particular thread needs to be awakened. This solution turns out to be more efficient because only the desired thread will be thread is awakened.

Risk Assessment

Signaling a single thread instead of all waiting threads can pose a threat to the liveness property of the system.

...

The CERT Oracle Secure Coding Standard for Java: THI04-J. Notify all waiting threads rather than a single thread

...

Sources

[Open Group] pthread_cond_signal() pthread_cond_broadcast()

...