Coursal
Coursal

Reputation: 1387

Condition variable never initialized/stuck in a deadlock

A while ago I tested out the way semaphores work with a simple program, where 3 threads (each called by its function) synchronize in a way that the output looks like:

<ONE><TWO><THREE><ONE><TWO><THREE><ONE><TWO><THREE><ONE><TWO><THREE><ONE><TWO><THREE>...

(code here for everyone interested or as a reference point)

I tried to recreate the very same program using condition variables and things got a littly-bity tricky.

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


pthread_cond_t condvar1=PTHREAD_COND_INITIALIZER;
pthread_cond_t condvar2=PTHREAD_COND_INITIALIZER;
pthread_cond_t condvar3=PTHREAD_COND_INITIALIZER;

pthread_mutex_t MUT=PTHREAD_MUTEX_INITIALIZER;

int loop=3;

void *one()
{
    int i;

    for(i=0;i<loop;i++)
    {
        pthread_mutex_lock(&MUT);
        if(pthread_cond_signal(&condvar3)==0)
                    pthread_cond_wait(&condvar1, &MUT);    
        pthread_mutex_unlock(&MUT);

                printf("<ONE>");

        pthread_mutex_lock(&MUT);    
            pthread_cond_signal(&condvar2);    
        pthread_mutex_unlock(&MUT);
    }

    return(NULL);
}

void *two()
{
    int i;

    for(i=0;i<loop;i++)
    {
        pthread_mutex_lock(&MUT);
        if(pthread_cond_signal(&condvar1)==0)  
                    pthread_cond_wait(&condvar2, &MUT);    
        pthread_mutex_unlock(&MUT);

                printf("<TWO>");

        pthread_mutex_lock(&MUT);    
            pthread_cond_signal(&condvar3);    
        pthread_mutex_unlock(&MUT);
    }

    return(NULL);
}

void *three()
{
    int i;

    for(i=0;i<loop;i++)
    {
        pthread_mutex_lock(&MUT);
        if(pthread_cond_signal(&condvar2)==0)   
                    pthread_cond_wait(&condvar3, &MUT);    
        pthread_mutex_unlock(&MUT);

                printf("<THREE>");

        pthread_mutex_lock(&MUT);    
            pthread_cond_signal(&condvar1);    
        pthread_mutex_unlock(&MUT);
    }

    return(NULL);
}


int main(int argc, char *argv[])
{
    pthread_t ena, dyo, tria;

    pthread_create(&ena, NULL, one, NULL);
    pthread_create(&dyo, NULL, two, NULL);
    pthread_create(&tria, NULL, three, NULL);

    pthread_join(ena, NULL);
    pthread_join(dyo, NULL);
    pthread_join(tria, NULL);

    return 0;
}

Basically I got a problem with initializing the first variable (condvar1) in order the synchronization to begin, since the hugely convenient sem_init() is out of the question.

Later I though that besides that, I could check if the previous condition variable (if(pthread_cond_signal(&previousconditionvariable)==0)) has released its lock or not and then I could lock the next condition variable.

But still, no proper initialization for condvar1 (for as much as I searched here, here, here, here) and of course no output.

Any ideas on initializing and/or tips for checking the previous' condition variable signal?

UPDATE: Removed the previous ifs and got a flag at their places like that:

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


pthread_cond_t condvar1=PTHREAD_COND_INITIALIZER;
pthread_cond_t condvar2=PTHREAD_COND_INITIALIZER;
pthread_cond_t condvar3=PTHREAD_COND_INITIALIZER;

pthread_mutex_t MUT=PTHREAD_MUTEX_INITIALIZER;


int flag;
int loop=3;

void *one()
{
    int i;

    for(i=0;i<loop;i++)
    {
        pthread_mutex_lock(&MUT);
        if(flag==1)
        {
                    pthread_cond_wait(&condvar1, &MUT);
        }   
        pthread_mutex_unlock(&MUT);

                printf("<ONE>");
        flag=2;

        pthread_mutex_lock(&MUT);    
            pthread_cond_signal(&condvar2);    
        pthread_mutex_unlock(&MUT);
    }

    return(NULL);
}

void *two()
{
    int i;

    for(i=0;i<loop;i++)
    {
        pthread_mutex_lock(&MUT);
        if(flag==2)  
        {
                    pthread_cond_wait(&condvar2, &MUT);

        }   
        pthread_mutex_unlock(&MUT);

                printf("<TWO>");
        flag=3;

        pthread_mutex_lock(&MUT);    
            pthread_cond_signal(&condvar3);    
        pthread_mutex_unlock(&MUT);
    }

    return(NULL);
}

void *three()
{
    int i;

    for(i=0;i<loop;i++)
    {
        pthread_mutex_lock(&MUT);
        if(flag==3)  
        {
                    pthread_cond_wait(&condvar3, &MUT);

        }    
        pthread_mutex_unlock(&MUT);

                printf("<THREE>");
        flag=1;

        pthread_mutex_lock(&MUT);    
            pthread_cond_signal(&condvar1);    
        pthread_mutex_unlock(&MUT);
    }

    return(NULL);
}


int main(int argc, char *argv[])
{
    pthread_t ena, dyo, tria;

    flag=1;

    pthread_create(&ena, NULL, one, NULL);
    pthread_create(&dyo, NULL, two, NULL);
    pthread_create(&tria, NULL, three, NULL);

    pthread_join(ena, NULL);
    pthread_join(dyo, NULL);
    pthread_join(tria, NULL);

    return 0;
}

now the output is <THREE><THREE><THREE><TWO><TWO><TWO><ONE><ONE><ONE>

Upvotes: 0

Views: 290

Answers (1)

Jonathan Leffler
Jonathan Leffler

Reputation: 753505

This code is a simplification of your code, and I believe it works as desired. There's a single thread function which is controlled by its argument. There's only one condition variable, too. The turn variable dictates which thread's turn it is. The manipulation of the turn is done while the thread has the mutex locked, so no other thread is playing with it at the same time. The printing is likewise done while the mutex is locked, to ensure it occurs at the correct time.

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

static pthread_cond_t condvar = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t MUT = PTHREAD_MUTEX_INITIALIZER;

static int turn;
static int loop = 3;

struct Control
{
    int thread_num;
    const char *msg;
};

static void *thread_func(void *arg)
{
    struct Control *ctl = arg;
    printf("Thread %d (%s)\n", ctl->thread_num, ctl->msg);

    for (int i = 0; i < loop; i++)
    {
        pthread_mutex_lock(&MUT);
        while (turn != ctl->thread_num)
            pthread_cond_wait(&condvar, &MUT);

        printf("<%s>", ctl->msg);
        turn++;
        if (turn > 3)
            turn = 1;
        pthread_cond_broadcast(&condvar);

        pthread_mutex_unlock(&MUT);
    }

    return(NULL);
}

int main(void)
{
    pthread_t ena, dyo, tri;
    struct Control ctl[] =
    {
        { 1, "ONE"   },
        { 2, "TWO"   },
        { 3, "THREE" },
    };

    srand(time(0));
    turn = rand() % 3 + 1;
    printf("%d goes first\n", turn);

    pthread_create(&ena, NULL, thread_func, &ctl[0]);
    pthread_create(&dyo, NULL, thread_func, &ctl[1]);
    pthread_create(&tri, NULL, thread_func, &ctl[2]);

    pthread_join(ena, NULL);
    pthread_join(dyo, NULL);
    pthread_join(tri, NULL);

    putchar('\n');

    return 0;
}

On my MacBook Pro running macOS Sierra 10.12.4 (using GCC 7.1.0, not that the compiler is all that important here), I get results such as this from consecutive runs (but there were indeterminate multi-second gaps between the runs):

2 goes first
Thread 1 (ONE)
Thread 2 (TWO)
Thread 3 (THREE)
<TWO><THREE><ONE><TWO><THREE><ONE><TWO><THREE><ONE>

3 goes first
Thread 1 (ONE)
Thread 2 (TWO)
Thread 3 (THREE)
<THREE><ONE><TWO><THREE><ONE><TWO><THREE><ONE><TWO>

3 goes first
Thread 1 (ONE)
Thread 2 (TWO)
Thread 3 (THREE)
<THREE><ONE><TWO><THREE><ONE><TWO><THREE><ONE><TWO>

1 goes first
Thread 1 (ONE)
Thread 2 (TWO)
Thread 3 (THREE)
<ONE><TWO><THREE><ONE><TWO><THREE><ONE><TWO><THREE>

3 goes first
Thread 2 (TWO)
Thread 1 (ONE)
Thread 3 (THREE)
<THREE><ONE><TWO><THREE><ONE><TWO><THREE><ONE><TWO>

Note that one time, thread 2 reported before thread 1.

I expect it would be possible to use three condition variables as in your original code, but it seems like overkill.

The error checking on the pthread calls is … non-existent. That's sort-of-OK in example code, at least when it's working, but not a good idea in general.

Upvotes: 1

Related Questions