0

I am been trying to write a program (for learning) in which there will be two threads (A and B) and both threads should execute one after the another. For example, if threads just display/prints Thread A and Thread B, then they should print in that particular order forever.

The desired output is

In Thread: thread1
In Thread: thread2
In Thread: thread1
In Thread: thread2
....

The program that I have wrote uses conditional variables for synchronisation. I have tired mutex and semaphore but they do guarantee mutual exclusivity but they don't print the information in a particular order. I understand that issue is related to scheduling of the threads by scheduler and it is possible that the thread which has just released the mutex, can lock it again immediately. See this link for link for more information.

#include <stdio.h>

#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>

#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;

int thread1_ret = 0;
void *thread1(void *arg)
{
    while (1) {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);

        printf("In Thread: %s\r\n", __func__);

        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
    thread1_ret = 5;
    return &thread1_ret;
}

int thread2_ret = 0;
void *thread2(void *arg)
{
    pthread_mutex_lock(&mutex);
    pthread_cond_signal(&cond);
    pthread_mutex_unlock(&mutex);
    while (1) {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);

        printf("In Thread: %s\r\n", __func__);

        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
    thread2_ret = 5;
    return &thread2_ret;
}

int main(int argc, char *argv[])
{
    pthread_t t1, t2;
    pthread_attr_t attr;

    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);


    pthread_cond_init(&cond, NULL);
    pthread_mutex_init(&mutex, NULL);

    pthread_create(&t1, &attr, thread1, NULL);
    pthread_create(&t2, &attr, thread2, NULL);

    pthread_attr_destroy(&attr);

    void *ret;
    pthread_join(t1, &ret);
    printf("Thread Returned: %d\r\n", *(int *)ret);
    pthread_join(t2, &ret);
    printf("Thread Returned: %d\r\n", *(int *)ret);

    return 0;
}

My program is working properly but it stops printing after some time (2-3 seconds). I couldn't locate the bug in my code. It would be great if someone direct me with some other solution to achieve the same thing in more efficient and standard method (if there are other standard and efficient methods to solve such problem statement).

abhiarora
  • 9,743
  • 5
  • 32
  • 57
  • 1
    I am not sure what is the exact scenario of lost wake-up here, but generally you usually need to put `pthread_cond_wait` in a loop to deal with spurious wake-ups. For example `while(current_turn != 1) { pthread_cond_wait(&cond, &mutex); }` – zch Apr 27 '19 at 18:52
  • This is probably just an experiment for you but I wanted to point out that wanting threads to execute in any particular order is generally a waste of time. You may want a group of threads to all finish before executing another stage that relies on their results, but if you have two or three threads that have to execute in order you may as well make it single-threaded: it will be easier. – Zan Lynx Apr 27 '19 at 19:21
  • @ZanLynx I understand that they can be in a single-thread. I wanted to learn conditional variables and thought of this experiment as I wasn't able to achieve it using semaphore and mutex. Is it possible using semaphore and mutex? – abhiarora Apr 27 '19 at 19:23

1 Answers1

2

Condition variable notifications get lost when no thread is waiting in pthread_cond_wait and spurious wakes-ups happen, so the code must rather wait for a change of a shared state.

Working example:

#include <stdio.h>

#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>

#include <unistd.h>
#include <semaphore.h>
#include <pthread.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
unsigned state = 0;

int thread1_ret = 0;
void *thread1(void *arg)
{
    unsigned state_copy;
    pthread_mutex_lock(&mutex);
    state_copy = state;
    pthread_mutex_unlock(&mutex);

    while(1) {
        pthread_mutex_lock(&mutex);
        while(state_copy == state)
            pthread_cond_wait(&cond, &mutex);
        state_copy = ++state;

        printf("In Thread: %s\r\n", __func__);

        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    thread1_ret = 5;
    return &thread1_ret;
}

int thread2_ret = 0;
void *thread2(void *arg)
{
    unsigned state_copy;
    pthread_mutex_lock(&mutex);
    state_copy = ++state;
    pthread_mutex_unlock(&mutex);
    pthread_cond_signal(&cond);

    while (1) {
        pthread_mutex_lock(&mutex);
        while(state_copy == state)
            pthread_cond_wait(&cond, &mutex);
        state_copy = ++state;

        printf("In Thread: %s\r\n", __func__);

        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&cond);
    }
    thread2_ret = 5;
    return &thread2_ret;
}

int main(int argc, char *argv[])
{
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);

    void *ret;
    pthread_join(t1, &ret);
    printf("Thread Returned: %d\r\n", *(int *)ret);
    pthread_join(t2, &ret);
    printf("Thread Returned: %d\r\n", *(int *)ret);

    return 0;
}

Note that the above code signals the condition variable after releasing the mutex. That is a micro-optimization, however, if FIFO order in waking up waiting threads is required then the mutex must be locked while signalling. See pthread_cond_signal:

The pthread_cond_broadcast() or pthread_cond_signal() functions may be called by a thread whether or not it currently owns the mutex that threads calling pthread_cond_wait() or pthread_cond_timedwait() have associated with the condition variable during their waits; however, if predictable scheduling behavior is required, then that mutex shall be locked by the thread calling pthread_cond_broadcast() or pthread_cond_signal().

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
  • Fedora29 `man pthread_cond_timedwait`: *Spurious wakeups from the pthread_cond_timedwait() or pthread_cond_wait() functions may occur.* Also [POSIX](https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_cond_wait.html) *Spurious wakeups from the pthread_cond_wait() or pthread_cond_timedwait() functions may occur.* – EOF Apr 27 '19 at 19:06
  • "Spurious" wakeups happen when several threads are notified, but one of them changes the condition before the others see it. They also happen when signals interrupt the wait. They also happen when a CPU architecture can only wait on changes to an entire cache line, and something changes the cache line. – Zan Lynx Apr 27 '19 at 19:18
  • @ZanLynx Spurious wakeup is when `pthread_cond_wait` returns but no `pthread_cond_signal` or `pthread_cond_broadcast` call was made. _one of them changes the condition before the others see it_ is not a spurious wakeup. – Maxim Egorushkin Apr 27 '19 at 19:20
  • @MaximEgorushkin : It is working now and I now understand the `Spurious wakeup` as well. I want to ask if there are other ways to achieve same thing like with only mutex or semaphore? – abhiarora Apr 27 '19 at 19:26
  • More about spurious wakeups: https://sourceware.org/git/?p=glibc.git;a=blob;f=nptl/pthread_cond_wait.c;hb=c57afec0a9b318bb691e0f5fa4e9681cf30df7a4#l108 Search the source for word "spurious". – Maxim Egorushkin Apr 27 '19 at 19:27
  • But I can't guarantee that particular order. I tried but same thread can lock the mutex multiple consecutive times. – abhiarora Apr 27 '19 at 19:31
  • @abhiarora Mutex is a binary semaphore, however, you cannot wait on a condition variable with a semaphore. IMO, semaphores are an obsolete abstraction (they predate `pthread`) not used in modern code. You can implement a semaphore using a mutex and a condition variable: https://stackoverflow.com/a/4793662/412080 – Maxim Egorushkin Apr 27 '19 at 19:34
  • I tried the same program with only mutex or only semaphore. I was able to have mutual exclusivity but I wasn't able to print in that particular order. – abhiarora Apr 27 '19 at 19:39
  • @MaximEgorushkin I disagree with your terminology. Any time the condition wakes up but the condition variable is false, is spurious. For whatever reason it happens. – Zan Lynx Apr 27 '19 at 22:25
  • @ZanLynx _Any time the condition wakes up but the condition variable is false_ - condition variables have no state, they are merely a notification mechanism. – Maxim Egorushkin Apr 27 '19 at 22:58
  • @ZanLynx _I disagree with your terminology_ - this is not a matter that requires your agreement, this is a POSIX definition of a spurious wakeup. In the future, when you make claims like that, please do research and provide supporting evidence. – Maxim Egorushkin Apr 27 '19 at 23:21