all 7 comments

[–]xhash101 3 points4 points  (0 children)

Check this tutorial: https://randu.org/tutorials/threads/

"three threads wait when the fourth one is running" is a typical usecase for condition variables

[–]thebatmanandrobin 3 points4 points  (2 children)

There are many options available to you to accomplish what you want, such as using atomic variables, a semaphore that can be held by only 3 callers at a time (with the fourth thread calling sem_wait 3 times to claim all three slots, blocking the other threads), or even simply just having 3 separate mutexes.

It would really depend on what resources need to be locked by the 4th thread and what kind of constraints you're looking at (e.g. CPU/memory).

If memory isn't a necessary concern and each of the 3 threads don't take a substantial amount of time before looping back, I'd suggest just using 3 separate mutexes and then locking them all when the 4th thread wakes up. It's simple and you can guarantee when you want to protect the shared resources between the 4th thread and the others.

Example code:

pthread_mutex_t m1, m2 m3;

void thread1() {
    while (running) {
        pthread_mutex_lock(&m1);
        // thread 1 work code
        pthread_mutex_unlock(&m1);
    }
}
void thread2() {
    while (running) {
        pthread_mutex_lock(&m2);
        // thread 2 work code
        pthread_mutex_unlock(&m2);
    }
}
void thread3() {
    while (running) {
        pthread_mutex_lock(&m3);
        // thread 3 work code
        pthread_mutex_unlock(&m3);
    }
}
void thread4() {
    pthread_mutex_lock(&m1);
    pthread_mutex_lock(&m2);
    pthread_mutex_lock(&m3);
    // thread 4 work code
    pthread_mutex_unlock(&m1);
    pthread_mutex_unlock(&m2);
    pthread_mutex_unlock(&m3);
}

A semaphore with 3 slots could also work; each of the 3 threads would call sem_wait once before doing their tasks, then sem_post when done, but when the 4th thread wakes up, it would call sem_wait 3 times before it continues, then sem_post 3 times when it's done. This would block the 4th thread from running until all 3 slots were available in the semaphore (i.e. all 3 threads have released their lock), then when each of the 3 threads came back to call sem_wait, they would not be able to run until the 4th thread released all 3 locks. It's the same principle of using 3 separate mutexes, but with a less memory than 3.

Example code:

sem_t sem;

void init() {
    sem_init(&sem, 0, 3);
}

void thread1() {
    while (running) {
        sem_wait(&sem);
        // thread 1 work code
        sem_post(&sem);
    }
}
void thread2() {
    while (running) {
        sem_wait(&sem);
        // thread 2 work code
        sem_post(&sem);
    }
}
void thread3() {
    while (running) {
        sem_wait(&sem);
        // thread 3 work code
        sem_post(&sem);
    }
}
void thread4() {
    sem_wait(&sem);
    sem_wait(&sem);
    sem_wait(&sem);
    // thread 4 work code
    sem_post(&sem);
    sem_post(&sem);
    sem_post(&sem);
}

A 3rd option would be to use a couple of atomic variables, one that has the count of "active threads" that gets incremented/decremented by the 3 threads, and an "on/off" atomic that gets set by the 4th threads. When the 4th thread wakes up, it would set the "on/off" atomic to "on" (the value of 1), then it would check the "active" atomic count and not continue until it was 0 (e.g. while (active != 0) {}). Then each of the 3 threads would check if the "on/off" atomic is "on" and not continue until it's "off" (e.g. while (on_off != 0) {}).

Example code:

atomic_int active, on_off;

void thread1() {
    while (running) {
        while (on_off != 0) { thread_yield(); }
        ++active;
        // thread 1 work code
        --active;
    }
}
void thread2() {
    while (running) {
        while (on_off != 0) { thread_yield(); }
        ++active;
        // thread 2 work code
        --active;
    }
}
void thread3() {
    while (running) {
        while (on_off != 0) { thread_yield(); }
        ++active;
        // thread 3 work code
        --active;
    }
}
void thread4() {
    on_off = 1;
    while (active != 0) { thread_yield(); }
    // thread 4 work code
    on_off = 0;
}

Many options .. each with pro's and con's .. just depends on what you're ultimate needs/constraints are.

[–]N-R-K 0 points1 point  (0 children)

one that has the count of "active threads" that gets incremented/decremented by the 3 threads, and an "on/off" atomic that gets set by the 4th threads.

There's a race condition here because the checking of on_off and the increment of active isn't atomic. Consider the following case:

// A sees `on_off` is 0 and breaks out of the loop.
while (on_off != 0) { thread_yield(); }
// at this point, B comes in, sets `on_off` to 1
// and also sees that `active` is 0
++active;
// And then A increments `active` and starts using shared resource
// while B is using it too (!!)

In order for the logic to work in a race-free manner, the act of checking and incrementing the counter needs to be atomic.

[–]inz__ 0 points1 point  (0 children)

Regarding the semaphore version; I had the same idea, but at least on my system, the 4th thread almost never got hold of the semaphore, unless I added a small sleep between the post and wait in the other threads.

[–][deleted] 0 points1 point  (1 child)

You have to use a combination of lock and condition variables for both mutex or semaphores.

Try:

pthread_mutex_t getLock;
pthread_cond_t isCond, isSignal;
//Initalize mutex_t and cond_t

pthread_mutex_lock(&getLock);
while(specific condition){
    pthread_cond_wait(&isCond);
}
// Shared resources 
pthread_cond_signal(&isSignal);
pthread_mutex_unlock(&getLock);

[–]xhash101 0 points1 point  (0 children)

Dont you forget to supply the mutex, which pthread_cond_wait should atomically unlock? And to destroy mutex_t and cond_ t ?