0

In the following multi producer and single consumer fixed size queue implementation i see the queue being overwritten by producers.

Why is the queue being overwritten?

At this point am not looking at performance of the code but would like to see that every item pushed is consumed and is not lost.

How do i extend this to multi prod multi consumer?

#include <iostream>
#include <thread>
#include <mutex>
#include <utility>
#include <ctime>
#include <condition_variable>
#include <stdlib.h>
#include <unistd.h>
#include <atomic>

using namespace std;

enum class returncode { QUEUE_EMPTY, QUEUE_FULL,SUCCESS } ;
atomic_long d;

class Queue {
  private:
    long* qdata;
    atomic_size_t length;
    atomic_size_t head;
    atomic_size_t tail ;
    atomic_size_t size;
    std::mutex qmutex;
    std::condition_variable qcond ;

  public:
    Queue(size_t len): length(len)
  {
    qdata=new long[length] ; 
    head=0;
    tail=0;
    size=0;
    srand(time(NULL));
  }

    bool isfull()
    {
      if( size == length )
        return true;

      return false;
    }

    bool isempty()
    {
      if(size == 0)
        return true;

      return false;
    }

    returncode push(long data)
    {
      std::unique_lock<std::mutex> qguard(qmutex);

      if(size == length) //if isfull
        return returncode::QUEUE_FULL ;

      qdata[head]=data;
      head = (head+1) % length; 
      size += 1 ;
      qcond.notify_one();
      qguard.unlock();     //Should the unlock come before notify_one? 
      return returncode::SUCCESS; 
    }

    //Its the users responsibility to check isempty before calling front.
    //Read function
    long front(void)
    {
      std::unique_lock<std::mutex> qguard(qmutex);
      //if isempty
      if( size == 0 )
        return 0;

      long d=qdata[tail];
      return d;
    }

    void pop(void)
    {
      std::thread::id thisthreadid = std::this_thread::get_id();

      std::unique_lock<std::mutex> qguard(qmutex);

      while(size == 0 ) // check condition to be safe against spurious wakes 
      {
        qcond.wait(qguard);// release lock and go join the waiting thread queue 
        return;
      }
      tail =(tail+1) % length;
      size -= 1;

    }
};

void produce(Queue* q)
{
  while(true)
  {
    d++;
    q->push(d) ;
    //sleep(1);
  }
}

void consume(Queue* q)
{
  while(true){
    //usleep(1000);
    cout <<"Consume: Pop " <<  q->front() <<endl;
    q->pop();
  }
}

int main(int argc, char** argv )
{
  Queue q(50);
  d=0;
  int nprods=4;
  int nconsu=1;

  std::thread producers[nprods];
  std::thread consumers[nconsu];

  for (int i=0; i < nprods ; ++i)
    producers[i] = std::thread(produce, &q);

  for (int i=0; i < nconsu ; ++i)
    consumers[i] = std::thread(consume, &q);

  for (int i=0; i<nprods; ++i)
    producers[i].join();

  for (int i=0; i<nconsu; ++i)
    consumers[i].join();

  return 0;
}

Output:

Consume: Pop 1
Consume: Pop 2
... Sequential all upto the queue size and then q is over written. 
Consume: Pop 49
Consume: Pop 50
Consume: Pop 51
Consume: Pop 52
Consume: Pop 53
Consume: Pop 54
Consume: Pop 55
Consume: Pop 56
Consume: Pop 64  --> 57 to 63 lost
Consume: Pop 72  --> 65 to 71 lost
Consume: Pop 81
Consume: Pop 89
Consume: Pop 97
Consume: Pop 105
vishu
  • 71
  • 2
  • 7
  • In a concurrent queue, you don't want `front` and `pop` to be separate operations - you want "pop the top element and return its value" to be a single atomic operation. Otherwise, two threads that do `auto elem = q.front(); q.pop();` at about the same time will both read the same element, then remove two elements - the one they both read, and the one that nobody read. – Igor Tandetnik Jul 12 '18 at 20:21
  • `qcond.wait(qguard); return;` This doesn't make any sense. Why are you waiting for something to happen, if you aren't going to act when it finally does? Why is this in a loop if you are going to bail out on the first iteration anyway? – Igor Tandetnik Jul 12 '18 at 20:24
  • `produce` doesn't check the return value of `push`. If the queue is full when it tries to push value `x`, then it simply drops `x` on the floor and proceeds to trying to push `x+1`. That's why you have gaps in your sequence. It's kind of weird that `pop` waits until an element is available if the queue is empty, but `push` doesn't wait until space becomes available if the queue is full. Did you mean this asymmetry? – Igor Tandetnik Jul 12 '18 at 20:27
  • Thanks @IgorTandetnik. I removed front and added the functionality into pop. What you said makes sense. Producer irrespective of the return value increments and pushes the data that way i miss to push those values into the queue and miss the sequence. Incrementing the data being pushed outside the critical section also seems to be a problem. – vishu Jul 12 '18 at 22:29

0 Answers0