0

I am trying to learn how to use pthreads following Douglas Niehaus' classes.

Now I'm in the middle of the producer-consumer problem. This is the solution I've reached so far:

PRODUCER:

void *producer (void *parg) {
  queue  *fifo;
  int     item_produced;
  pcdata *mydata;
  int     my_tid;
  int    *total_produced;

  mydata = (pcdata *) parg;

  fifo           = mydata->q;
  total_produced = mydata->count;
  my_tid         = mydata->tid;

  /*
  * Continue producing until the total produced reaches the
  * configured maximum
  */
  while (1) {
    /*
    * Do work to produce an item. Tthe get a slot in the queue for
    * it. Finally, at the end of the loop, outside the critical
    * section, announce that we produced it.
    */
    do_work(PRODUCER_CPU, PRODUCER_BLOCK);

    pthread_mutex_lock(fifo->mutex);
    /*
    * If the queue is full, we have no place to put anything we
    * produce, so wait until it is not full.
    */

    while(fifo->full && *total_produced != WORK_MAX) {
      pthread_cond_wait(fifo->notFull,fifo->mutex);
    }

    if(*total_produced >= WORK_MAX) {
      printf("PRODUCER %d is exitting because the production reached the MAX\n",my_tid);
      pthread_cond_signal(fifo->notFull);
      pthread_mutex_unlock(fifo->mutex);
      break;
    }

    /*
    * OK, so we produce an item. Increment the counter of total
    * widgets produced, and add the new widget ID, its number, to the
    * queue.
    */
    item_produced = (*total_produced)++;
    queueAdd (fifo, item_produced);

    pthread_cond_signal(fifo->notEmpty);
    pthread_mutex_unlock(fifo->mutex);

    /*
    * Announce the production outside the critical section 
    */
    printf("prod %d:\t %d.\n", my_tid, item_produced);

 }

printf("prod %d:\texited\n", my_tid);
return (NULL);
}

CONSUMER:

    void *consumer (void *carg)
    {
      queue  *fifo;
      int     item_consumed;
      pcdata *mydata;
      int     my_tid;
      int    *total_consumed;

      mydata = (pcdata *) carg;

      fifo           = mydata->q;
      total_consumed = mydata->count;
      my_tid         = mydata->tid;

      /*
       * Continue producing until the total consumed by all consumers
       * reaches the configured maximum
       */
      while (1) {
        /*
         * If the queue is empty, there is nothing to do, so wait until it
         * si not empty.
         */
        pthread_mutex_lock(fifo->mutex);

        while(fifo->empty && *total_consumed != WORK_MAX) {
          pthread_cond_wait(fifo->notEmpty,fifo->mutex);
        }

        if(*total_consumed >= WORK_MAX) {
          printf("CONSUMER %d is exitting because the compsuption reached the MAX\n",my_tid);
          pthread_cond_signal(fifo->notEmpty);
          pthread_mutex_unlock(fifo->mutex);
          break;
        }

        /*
         * Remove the next item from the queue. Increment the count of the
         * total consumed. Note that item_consumed is a local copy so this
         * thread can retain a memory of which item it consumed even if
         * others are busy consuming them. 
         */
          queueRemove (fifo, &item_consumed);
          (*total_consumed)++;
          pthread_cond_signal(fifo->notFull);
          pthread_mutex_unlock(fifo->mutex);


        /*
         * Do work outside the critical region to consume the item
         * obtained from the queue and then announce its consumption.
         */
        do_work(CONSUMER_CPU,CONSUMER_CPU);
        printf ("con %d:\t %d.\n", my_tid, item_consumed);

      }

      printf("con %d:\texited\n", my_tid);
      return (NULL);
    }

This seems to work fine with rather small number of producers and consumers, for example 100 consumers and 50 producers or viceversa. But if I try the code with more than 2000 producers and/or consumers, the execution hangs. I think I've reached a deadlock but I'm unable to find it.

Any help would be appreciated.

NOTE:

The signals inside the if-block:

if(*total_consumed >= WORK_MAX) { ... }

and the if-block:

if(*total_consumed >= WORK_MAX) {...}

are something that I added because the execution without them would hang up with more than 5 producers and/or consumers.

My reasoning is that if the work limit has been reached, the other producers should be told so they can exit too. And the same goes for the consumers.

NOTE 2:

The following code is already provided to the student:

typedef struct {
  int buf[QUEUESIZE];   /* Array for Queue contents, managed as circular queue */
  int head;             /* Index of the queue head */
  int tail;             /* Index of the queue tail, the next empty slot */  

  int full;             /* Flag set when queue is full  */
  int empty;            /* Flag set when queue is empty */

  pthread_mutex_t *mutex;     /* Mutex protecting this Queue's data */
  pthread_cond_t  *notFull;   /* Used by producers to await room to produce*/
  pthread_cond_t  *notEmpty;  /* Used by consumers to await something to consume*/
} queue;

typedef struct {
  queue *q;       
  int   *count;   
  int    tid;
} pcdata;

/******************************************************/

queue *queueInit (void) {
  queue *q;

 /*
 * Allocate the structure that holds all queue information
 */
q = (queue *)malloc (sizeof (queue));
if (q == NULL) return (NULL);

/*
 * Initialize the state variables. See the definition of the Queue
 * structure for the definition of each.
 */
 q->empty = 1;  
 q->full  = 0;   

 q->head  = 0;   
 q->tail  = 0;   

 /*
  *   Allocate and initialize the queue mutex
  */
  q->mutex = (pthread_mutex_t *) malloc (sizeof (pthread_mutex_t));
  pthread_mutex_init (q->mutex, NULL);

  /*
   * Allocate and initialize the notFull and notEmpty condition
   * variables
   */
  q->notFull = (pthread_cond_t *) malloc (sizeof (pthread_cond_t));
  pthread_cond_init (q->notFull, NULL);

  q->notEmpty = (pthread_cond_t *) malloc (sizeof (pthread_cond_t));
  pthread_cond_init (q->notEmpty, NULL);

  return (q);
}

And in main:

int main (int argc, char *argv[]) {
  // More initializations
  pthread_t *con;
  int        cons;
  int       *concount;

  queue     *fifo;
  int        i;

  pthread_t *pro;
  int       *procount;
  int        pros;

  pcdata    *thread_args;
  fifo = queueInit ();
  if (fifo ==  NULL) {
    fprintf (stderr, "main: Queue Init failed.\n");
    exit (1);
  }
  // More initializations and creation of the threads:

  for (i=0; i<pros; i++){ 
    /*
    * Allocate memory for each producer's arguments
    */
    thread_args = (pcdata *)malloc (sizeof (pcdata));
    if (thread_args == NULL) {
      fprintf (stderr, "main: Thread_Args Init failed.\n");
      exit (1);
    }

    /*
    * Fill them in and then create the producer thread
    */
    thread_args->q     = fifo;
    thread_args->count = procount;
    thread_args->tid   = i;
    pthread_create (&pro[i], NULL, producer, thread_args);
  }
}

}

Kio Marv
  • 337
  • 2
  • 3
  • 8
  • Just to clarify, how is the mutex created and how is it shared? is it a one mutex between one producer and one consumer or what? – o_weisman Oct 22 '13 at 13:21
  • I'll post that piece of the code. The code provided in that class already creates the mutex. – Kio Marv Oct 22 '13 at 13:24
  • Without seeing the creation and sharing code of the mutex, I find it odd that you lock the mutex and then wait on the queue to be not full. Say your producer has locked the mutex, finds the queue full and is now waiting on the consumers to remove one of the products while still holding the mutex. But the consumers can't get the mutex and remove the products because the producer is holding it. It's a deadlock. At least that's my guess. – o_weisman Oct 22 '13 at 13:29
  • Should I post more code? – Kio Marv Oct 22 '13 at 13:33
  • @o_weisman, I read [here](https://computing.llnl.gov/tutorials/pthreads/#ConditionVariables), in the code example, that `Note that the pthread_cond_wait routine will automatically and atomically unlock mutex while it waits.` So shouldn't the mutex be released when waiting? – Kio Marv Oct 22 '13 at 13:35
  • I stand corrected. Could there be an issue of spurious wakeups as stated here: [link](http://stackoverflow.com/questions/1050592/do-spurious-wakeups-actually-happen) ? – o_weisman Oct 22 '13 at 13:49
  • @o_weisman, So you think the code is allright? :D But the spurious wakeup shouldn't be dealt with the `while` loop? – Kio Marv Oct 22 '13 at 14:13
  • I didn't say that your code is alright ;-) . Personally I think that the produced number should be incremented before the queue mechanism since at the moment it seems to you might produce more than the max amount. Or better yet if you want to set a common max, move production itself into the protected section. I also think that it may be dangerous to check >= WORK_MAX in the if but check == WORK_MAX in the while. – o_weisman Oct 22 '13 at 14:32
  • @o_weisman. Ok. I changed the loop condition to `<`. Now about the production work,aka. `do_work`. It's a dummy method, it just sleeps the thread for an amount of time to simulate the production. And what do you mean about `before the queue mechanism`? Do you refer to `queueAdd (fifo, item_produced)`? if so, the produced counter is incremented before the call to that function. And in the case of the consumer, shouldn't it be as it is now? First remove the item and then increment the consumed counter? – Kio Marv Oct 22 '13 at 14:43
  • I'm guessing that didn't change anything? – o_weisman Oct 22 '13 at 21:53
  • @o_weisman Yeah! However I tried the code in Linux and it worked fine! In Windows calling `producer_consumer.exe 3000 50` would hang, but not in Linux! – Kio Marv Oct 23 '13 at 07:45
  • How is this supposed to run on Windows? Do you use a Linux emulator? – o_weisman Oct 23 '13 at 08:05
  • @o_weisman If you enter in the link at the top of my questions, you will get to the assignment page. There, there's a `tar.gz` file with the starter code and a `Makefile`. I started in Windows under the `Cygwin` environment. Then I tried the code in Ubuntu. – Kio Marv Oct 23 '13 at 08:19
  • When does the program hang? Can you tell by your printouts? Is it immedaitely? When the WORK_MAX is reached? Some other time? – o_weisman Oct 26 '13 at 13:39

0 Answers0