F. Coz
F. Coz

Reputation: 25

Proper method to wait for exit conditions in threads

So reading this topic, the common way to exit would be using a flag. My question is, how is the waiting handled? Say the thread is only to run every 30s, how would you wait those 30s properly?

Using sem_timedwait() isn't ideal as it relies on the system clock and any change to the clock can severely impact your application. This topic explains using condition variables instead. The problem is, it relies on a mutex. You can't safely use pthread_mutex_lock() and pthread_mutex_unlock() in a signal handler. So in terms of my example above of 30s, if you want to exit immediately, who is handling the mutex unlock?

My guess would be another thread that sole purpose is to check the exit flag and if true it would unlock the mutex. However, what is that thread like? Would it not be wasted resources to just sit there constantly checking a flag? Would you use sleep() and check every 1s for example?

I don't believe my guess is a good one. It seems very inefficient and I run into similar "how do I wait" type of question. I feel like I'm missing something, but my searching is leading to topics similar to what I linked where it talks about flags, but nothing on waiting.

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>

#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <unistd.h>

pthread_mutex_t my_mutex;
volatile sig_atomic_t exitRequested = 0;

void signal_handler(int signum) {
    exitRequested = 1;
}

bool my_timedwait(pthread_mutex_t *mutex, int seconds) {
    pthread_condattr_t attr;
    pthread_condattr_init(&attr);
    pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);

    pthread_cond_t cond;
    pthread_cond_init(&cond, &attr);

    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    ts.tv_sec += seconds;

    int status = pthread_cond_timedwait(&cond, mutex, &ts);
    if (status == 0) {
        return false; // mutex unlocked
    }

    if ((status < 0) && (status != ETIMEDOUT)) {
        // error, do something
        return false;
    }

    return true; // timedout
}

void *exitThread(void *ptr) {
    // constant check???
    while (1) {
      if (exitRequested) {
          pthread_mutex_unlock(&my_mutex);
          break;
      }
    }
}

void *myThread(void *ptr) {
    while (1) {
        // do work
        printf("test\n");

        // wait and check for exit (how?)
        if (!my_timedwait(&my_mutex, 30)) {
            // exiting
            break;
        }
    }
}

int main(void) {
    // init and setup signals
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigaction(SIGINT, &sa, NULL);

    // init the mutex and lock it
    pthread_mutex_init(&my_mutex, NULL);
    pthread_mutex_lock(&my_mutex);

    // start exit thread
    pthread_t exitHandler;
    pthread_create(&exitHandler, NULL, exitThread, NULL);

    // start thread
    pthread_t threadHandler;
    pthread_create(&threadHandler, NULL, myThread, NULL);

    // wait for thread to exit
    pthread_join(threadHandler, NULL);
    pthread_join(exitHandler, NULL);

    return EXIT_SUCCESS;
}

Upvotes: 1

Views: 1135

Answers (1)

David Schwartz
David Schwartz

Reputation: 182753

The solution is simple. Instead of having the first thread block in pthread_join, block that thread waiting for signals. That will ensure that a SIGINT can be handled synchronously.

You need a global structure protected by a mutex. It should count the number of outstanding threads and whether or not a shutdown is requested.

When a thread finishes, have it acquire the mutex, decrement the number of outstanding threads and, if it's zero, send a SIGINT. The main thread can loop waiting for a signal. If it's from the thread count going to zero, let the process terminate. If it's from an external signal, set the shutdown flag, broadcast the condition variable, unlock the mutex, and continue waiting for the thread count to hit zero.

Here's a start:

pthread_mutex_t my_mutex; // protects shared state
pthread_cond_t my_cond;   // allows threads to wait some time
bool exitRequested = 0;   // protected by mutex
int threadsRunning = 0;   // protected by mutex
pthread_t main_thread;    // set in main

bool my_timedwait(int seconds)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    ts.tv_sec += seconds;

    pthread_mutex_lock (&my_mutex);
    while (exitRequested == 0)
    {
        int status = pthread_cond_timedwait(&my_cond, &my_mutex, &ts);
        if (status == ETIMEDOUT) // we waited as long as supposed to
            break;
    }

    bool ret = ! exitRequested;
    pthread_mutex_unlock (&my_mutex);

    return ret; // timedout
}

bool shuttingDown()
{
    pthread_mutex_lock (&my_mutex);
    bool ret = exitRequested;
    pthread_mutex_unlock (&my_mutex);
    return ret;
}

void requestShutdown()
{
    // call from the main thread if a SIGINT is received
    pthread_mutex_lock (&my_mutex);
    exitRequested = 1;
    pthread_cond_broadcast (&my_cond);
    pthread_mutex_unlock (&my_mutex);
}

void threadDone()
{
    // call when a thread is done
    pthread_mutex_lock (&my_mutex);
    if (--threadsRunning == 0)
        pthread_kill(main_thread, SIGINT); // make the main thread end
    pthread_mutex_unlock (&my_mutex);
}

Upvotes: 1

Related Questions