简体   繁体   中英

Synchronization using Pthreads mutex and conditional variables in C

I am trying to create two threads resembling TaskA and TaskB. Both TaskA and TaskB do some kind of computation that it is not very interesting for this post. TaskA and TaskB have to be executed 10 times in order to cover the whole array. TaskA has an input AA and an output BB. BB is also the input of TaskB. CC is the output of TaskB. Because BB is written by taskA and read by taskB we need mutexes.

The behavior I would like to achieve is that when TaskA operates on i, TaskB operates on i-1 in parallel, where i is the number of arrays that are processed. I want to avoid TaskB to wait for TaskA to finish for every i.

The problem here is that I have a deadlock. ThreadA and ThreadB represent TaskA and TaskB. To make it easier I removed all the computations and I left only synchronization instructions. The deadlock is caused because ThreadA signals the conditional variable CV[0] before threadB is in the state that waits for CV[0].

Do you know any way to remove the deadlock but without TaskA waiting for TaskB to finish and vice versa. Ideally when TaskA operates on array i TaskB should operate on array i-1.

/* Includes */
#include <unistd.h>     /* Symbolic Constants */
#include <sys/types.h>  /* Primitive System Data Types */ 
#include <errno.h>      /* Errors */
#include <stdio.h>      /* Input/Output */
#include <stdlib.h>     /* General Utilities */
#include <pthread.h>    /* POSIX Threads */
#include <string.h>     /* String handling */
#include <semaphore.h>  /* Semaphore */
#include <stdint.h>

#define ARRAY_SIZE 2048*2400
#define DEBUG
//#define CHECK_RESULTS

pthread_mutex_t mutex[10];
pthread_cond_t cv[10];

/* prototype for thread routine */
void threadA ( void *ptr );
void threadB ( void *ptr );


struct thread_arg
{
    uint32_t *in;
    uint32_t *out;
    uint32_t ID;    
};

int main()
{

    pthread_t pthA;
    pthread_t pthB;
   //Memory allocation 
    uint32_t *AA = malloc(10*ARRAY_SIZE*sizeof(uint32_t)); 
    uint32_t *BB = malloc(10*ARRAY_SIZE*sizeof(uint32_t));
    uint32_t *CC = malloc(10*ARRAY_SIZE*sizeof(uint32_t)); 
    unsigned int j,i;

    // THread Arguments
    struct thread_arg arguments[2];
    arguments[0].in  = AA;
    arguments[0].out = BB;
    arguments[0].ID  = 1;
    arguments[1].in  = BB;
    arguments[1].out = CC;
    arguments[1].ID  = 2;
    //Init arguments data
    for (j=0;j<10;j++)
    {
        for (i=0;i<ARRAY_SIZE;i++)
        {
            AA[j*ARRAY_SIZE+i] = i;
            BB[j*ARRAY_SIZE+i] = 0;
            CC[j*ARRAY_SIZE+i] = 99 ; 
        }
    }   

    //Semaphore and conditional variables init
    for (i=0;i<10;i++){
        pthread_mutex_init(&mutex[i], NULL);
        pthread_cond_init (&cv[i], NULL);

    }

    pthread_create (&pthA, NULL, (void *) &threadA, (void *) &arguments[0]);
    pthread_create (&pthB, NULL, (void *) &threadB, (void *) &arguments[1]);

    pthread_join(pthA, NULL);
    pthread_join(pthB, NULL);

    // Destroy Semaphores and CVs
    for (i=0;i<10;i++)
    {
        pthread_mutex_destroy(&mutex[i]);
        pthread_cond_destroy(&cv[i]); 
    }       
    // Checking results 

    exit(0);
} /* main() */





void threadA ( void *ptr )
{
    int i; 
    struct thread_arg *arg = (struct thread_arg *) ptr;
    for (i=0;i<10;i++)
    {
        pthread_mutex_lock(&mutex[i]);
        printf("TA: LOCK_M%d  \n",i);
        pthread_cond_signal(&cv[i]);
        printf("TA: SIG_CV%d\n",i);
        pthread_mutex_unlock(&mutex[i]);
        printf("TA: UNL_M%d\n",i);
    }   
    pthread_exit(0); /* exit thread */
}




void threadB ( void *ptr )
{
    int i; 
    struct thread_arg *arg = (struct thread_arg *) ptr;

    for (i=0;i<10;i++)
    {
        pthread_mutex_lock(&mutex[i]);
        printf("TB: WAIT_CV%d\n",i,i);
        pthread_cond_wait(&cv[i], &mutex[i]);
        printf("TB CV%d_PASSED\n",i);
        pthread_mutex_unlock(&mutex[i]);
        printf("TB UNL_M%d \n",i);
    }

  pthread_exit(NULL);
}

As WhozCraig commented, a condition variable needs to be paired with a condition over some shared state, known as a predicate . The mutex is used to protect the shared state.

In this example, your shared state could be an integer that contains the highest index of BB[] that ThreadA has produced. ThreadB then waits for this number to reach the index that it is up to reading. In this design, you only need one mutex and one condition variable. The globals would then be:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
int BB_ready = -1;  /* Protected by 'mutex' */

(Using the static PTHREAD_*_INITIALIZER initialisers means that you don't need to bother with pthread_*_init() and pthread_*_destroy() ).

The loop in ThreadA would then be:

for (i=0;i<10;i++)
{
    /* Process AA[i] into BB[i] here */

    /* Now mark BB[i] as ready */
    pthread_mutex_lock(&mutex);
    printf("TA: LOCK_M%d  \n",i);
    BB_ready = i;
    pthread_cond_signal(&cv);
    printf("TA: SIG_CV%d\n",i);
    pthread_mutex_unlock(&mutex);
    printf("TA: UNL_M%d\n",i);
}

..and in ThreadB:

for (i=0;i<10;i++)
{
    /* Wait for BB[i] to be ready */
    pthread_mutex_lock(&mutex);
    printf("TB: WAIT_CV%d\n",i);
    while (BB_ready < i)
        pthread_cond_wait(&cv, &mutex);
    printf("TB CV%d_PASSED\n",i);
    pthread_mutex_unlock(&mutex);
    printf("TB UNL_M%d \n",i);

    /* Now process BB[i] into CC[i] here */
}

Notice that pthread_cond_signal() is called whenever the shared state has changed, which allows the other thread to wake up and re-check the state, if it's waiting.

The waiting thread always loops around, checking the state and then waiting on the condition variable if the state isn't ready yet.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM