0

I'm trying to implement the producer consumer with conditional variables so I can learn about synchronization. I'm using the github to guide me and solved some seg faults but now it seems that my consumer never gets executed or is stuck in deadlock. I'm not sure what could be the error. I have included printf statements in the producer to execute everytime it runs and it completes producing any string in messages.txt less than 5. The consumer though does not and is stuck in a deadlock.

 #define max 5

int par = 0;

// Data structure for queue
struct queue
{
    char *items;        // array to store queue elements
    int maxsize;    // maximum capacity of the queue
    int front;      // front points to front element in the queue (if any)
    int rear;       // rear points to last element in the queue
    int size;       // current capacity of the queue
    pthread_mutex_t mutex; // needed to add/remove data from the buffer
    pthread_cond_t can_produce; // signaled when items are removed
    pthread_cond_t can_consume; // signaled when items are added
};

// Utility function to initialize queue
struct queue* newQueue(int size)
{
    struct queue *pt = NULL;
    pt = (struct queue*)malloc(sizeof(struct queue));

    pt->items = (char*)malloc(size * sizeof(char));
    pt->maxsize = size;
    pt->front = 0;
    pt->rear = -1;
    pt->size = 0;
    pthread_mutex_init(&pt->mutex, NULL);
    pthread_cond_init(&pt->can_produce, NULL);
     pthread_cond_init(&pt->can_consume, NULL);
    return pt;
}

// Utility function to return the size of the queue
int size(struct queue *pt)
{
    return pt->size;
}

// Utility function to check if the queue is empty or not
int isEmpty(struct queue *pt)
{
    return !size(pt);
}

// Utility function to return front element in queue
char front(struct queue *pt)
{
    if (isEmpty(pt))
    {
        //printf("UnderFlow\nProgram Terminated\n");

    }

    return pt->items[pt->front];
}

// Utility function to add an element x in the queue
void enqueue(struct queue *pt, char x)
{
    if (size(pt) == pt->maxsize)
    {
        //printf("OverFlow\nProgram Terminated\n");

    }

    //printf("Inserting %c\t", x);

    pt->rear = (pt->rear + 1) % pt->maxsize;    // circular queue
    pt->items[pt->rear] = x;
    pt->size++;

    //printf("front = %c, rear = %c\n", pt->front, pt->rear);
}

// Utility function to remove element from the queue
void dequeue(struct queue *pt)
{
    if (isEmpty(pt)) // front == rear
    {
        //printf("UnderFlow\nProgram Terminated\n");

    }

    //printf("Removing  %c\t", front(pt));

    pt->front = (pt->front + 1) % pt->maxsize;  // circular queue
    pt->size--;

    //printf("front = %d, rear = %c\n", pt->front, pt->rear);
}

void consumer_f(void *arg)
{
    struct queue *pt = (struct queue*)arg;
     while (par==0 && !isEmpty(pt))
    {
        pthread_mutex_lock(&pt->mutex);
        if (pt->size == 0)
        { // empty
            // wait for new items to be appended to the buffer
            pthread_cond_wait(&pt->can_consume, &pt->mutex);
        }

        printf("%c", pt->front);
        dequeue(pt);
        pthread_cond_signal(&pt->can_produce);
        pthread_mutex_unlock(&pt->mutex);
    } 
}
void producer_f(void *arg)
{
    struct queue *pt = (struct queue*)arg;
     char tmp;
    FILE *fp;
    fp = fopen("messages.txt", "r");
    if (fp == NULL)
    {
        //fprintf(stderr, "error opening messages.txt");
        return -1;
    }
    while ((tmp = fgetc(fp)) != EOF)
    {
        pthread_mutex_lock(&pt->mutex);
        if (pt->size == max)
            pthread_cond_wait(&pt->can_produce, &pt->mutex);
        enqueue(pt, tmp);
        printf("sent");
        pthread_cond_signal(&pt->can_consume);
        pthread_mutex_unlock(&pt->mutex);
    }
    par = 1; //denotes EOF for consumer */
}
int main()
{
    printf("nop");
    struct queue *pt = newQueue(5);


    pthread_t producer;
    pthread_t consumer;
    printf("got here");
    if (pthread_create(&producer, NULL, &producer_f, pt))
    {
        //fprintf(stderr, "Error creating producer thread\n");
        return -1;
    }
      if (pthread_create(&consumer, NULL, &consumer_f, pt))
    {
        //fprintf(stderr, "Error creating consumer thread\n");
        return -1;
    } 
     if (pthread_join(producer_f, NULL))
    {
        //fprintf(stderr, "Error joining proucer thread\n");
        return -1;
    } 
     if (pthread_join(consumer_f, NULL))
    {
        //fprintf(stderr, "Error joinging consumer thread\n");
        return -1;
    }  

    return 0;
}
Nihal
  • 17
  • 1
  • 8
  • 1
    Don't use `printf()`. `stdout` is line-buffered if it refers to an interactive device, and fully buffered otherwise. Since not all of your debug-`printf()`s contain a `\n`, you should debug to `stderr` anyway. – EOF Mar 29 '19 at 00:15
  • Also, undefined behavior for unsynchronized, non-atomic, non-readonly access to an object from multiple threads. – EOF Mar 29 '19 at 00:22
  • I've changed all my debug printf() to frpintf() printing to stderr. What do you mean"undefined behavior for unsynchronized, non-atomic, non-readonly access to an object from multiple threads." Edit: are you talking about accessing ```int par``` in both consumer and producer? – Nihal Mar 29 '19 at 00:31
  • C11 draft standard n1570: *5.1.2.4 Multi-threaded executions and data races 4 Two expression evaluations conflict if one of them modifies a memory location and the other one reads or modifies the same memory location. 25 The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. Any such data race results in undefined behavior.* – EOF Mar 29 '19 at 00:34
  • I have a single file library of such thing you can use it as reference – geckos Mar 29 '19 at 00:35
  • https://github.com/dhilst/pq/blob/master/pq.h – geckos Mar 29 '19 at 00:35
  • Feel free to ask anything – geckos Mar 29 '19 at 00:35
  • @geckos thank you for providing the library but I don't quite understand how I can use it in my producer-consumer problem – Nihal Mar 29 '19 at 00:37
  • @EOF I've fixed all printf() and the variable par, still having problems. – Nihal Mar 29 '19 at 00:38
  • Is just an implementation of a queue, you can use it as a reference. I used this code in multiple consumer single producer in production. It uses conditional variables to notify consumer about enqueues – geckos Mar 29 '19 at 00:40
  • @geckos Forgive me, after looking at the code, I see it now. I don't know what could be wrong with my code, I do the same steps as your code(not the checking buffer/reading file) – Nihal Mar 29 '19 at 00:43
  • Producer call pq_put_head and consumer uses pq_get_tail, the wait boilerplate is where the wait happens. It is more complicated because it supports timeout – geckos Mar 29 '19 at 00:45
  • @geckos I've been looking at those methods for a while now and still see no difference with my code(timeouts disregarded). Do you know what could be wrong? – Nihal Mar 29 '19 at 00:55
  • You need to reevaluate the predicate after cond wait returns and re wait if the predicate isn't satisfied. From the docs: _When using condition variables there is always a boolean predicate involving shared variables associated with each condition wait that is true if the thread should proceed. Spurious wakeups from the pthread_cond_wait() or pthread_cond_timedwait() functions may occur. Since the return from pthread_cond_wait() or pthread_cond_timedwait() does not imply anything about the value of this predicate, the predicate should be re-evaluated upon such return._ – geckos Mar 29 '19 at 01:19
  • In your case pt->size seems to be the variable that you should test. – geckos Mar 29 '19 at 01:21

1 Answers1

0

The consumer thread is not going to deadlock state but it is exiting without consuming as EOS is reached before consumer started consuming.

As you know, threads can get scheduled in a random way by OS (I mean, at least you can assume that they get scheduled in a random way). With this assumption, the producer might have started and read all bytes and enabled eos flag i.e. par=1. If consumer thread starts after par=1, it is not consuming at all.

To handle this case, you need to update the consumer_f() function.

while (1) //Run the loop always
{
    pthread_mutex_lock(&pt->mutex);
    if ((pt->size == 0) && (par == 0)) //Make sure that EOS is not already reached.
    { // empty
        // wait for new items to be appended to the buffer
        pthread_cond_wait(&pt->can_consume, &pt->mutex);
    }

    if(par && isEmpty(pt))
    {
         //If Array is empty and eos is reached, unlock and exit loop
         pthread_mutex_lock(&pt->mutex);
         break;
    }

    //Other code
}

Similarly, you need to enable eos flag inside mutex in producer_f().

while ((tmp = fgetc(fp)) != EOF)
{
    //Your code
}

pthread_mutex_lock(&pt->mutex);
par = 1;
pthread_cond_signal(&pt->can_consume); //To make sure that consumer thread wakesup
pthread_mutex_unlock(&pt->mutex);

PS: pt->size == 0 can be replaced with isEmpty(pt) for more readability.

MayurK
  • 1,925
  • 14
  • 27