[Linux] producer consumer model

Posted by seeker2921 on Sat, 19 Feb 2022 10:40:25 +0100

summary

Producer consumer model: a typical design pattern, which is a solution designed by people according to typical scenes

Application scenario: it is applied to the scenario where a large amount of data is generated and processed

Specific implementation: there are multiple producer and consumer threads to produce and process a huge amount of data, but producers and consumers do not interact directly, but coordinate through the middle buffer; The data generated by the producer is stored in the buffer, and the consumer gets the data from the buffer and processes it.

advantage:
Decoupling: separate production and consumption. When the data processing mode changes, only change the consumers
Support uneven busy and idle: if the amount of production data is too large, it can be stored in the buffer for consumers to process slowly
Support Concurrency: you can open multiple producer threads or consumer threads to work

In order to support concurrency, thread safety must be guaranteed:
1. Each producer should maintain a mutually exclusive relationship; Each consumer should also maintain a mutually exclusive relationship;
2. Producers and consumers should maintain a synchronous and mutually exclusive relationship

realization

Bottom implementation: producer thread and consumer thread (one in the queue and one out of the queue) + the queue to ensure thread safety is used as the buffer

1. Thread safe queue implementation
Encapsulate a thread safe queue class: class BlockQueue - blocking queue
If there is an idle node, data can be queued, and if there is no, the queued thread will be blocked;
If there is a data node, the data can be dequeued, and if there is no data node, the dequeued thread will be blocked;

Idea:

class BlockQueue{
private:
 	int capacity;				//capacity
 	std::queue<int> _queue;		//The bottom layer encapsulates a queue
 	pthread_mutex_t mutex;		//mutex 
 	pthread_cond_t cond_pro;	//Producer condition variable
 	pthread_cond_t cond_cus;	//Consumer condition variable
public:
 	BlockQueue(int capacity=5);	//Constructor
 	bool push(int data);		//Join the team
 	bool pop(int* data);		//Out of the team
 	~BlockQueue();				//Deconstruction
};

2. Create a thread to queue and dequeue data

Full code:

#include<iostream>
#include<queue>
#include<pthread.h>
#include<cstdio>
#include<unistd.h>


class BlockQueue{
  private:
    int capacity;               //capacity
    std::queue<int> _queue;     //The bottom layer encapsulates a queue
    pthread_mutex_t mutex;      //mutex 
    pthread_cond_t cond_pro;    //Producer condition variable
    pthread_cond_t cond_cus;    //Consumer condition variable
public:

    BlockQueue(int cap=5):capacity(cap){
      pthread_mutex_init(&mutex,NULL);
      pthread_cond_init(&cond_pro,NULL);
      pthread_cond_init(&cond_cus,NULL);

    }

    bool Push(int data){
     pthread_mutex_lock(&mutex);
     while(_queue.size()==capacity){
       pthread_cond_wait(&cond_pro,&mutex);
     }
     _queue.push(data);
     pthread_cond_signal(&cond_cus);
     pthread_mutex_unlock(&mutex);
      return true;
    }

    bool Pop(int* data){
      pthread_mutex_lock(&mutex);
      while(_queue.empty()){
        pthread_cond_wait(&cond_cus,&mutex);
      }
      *data=_queue.front();
      _queue.pop();
      pthread_cond_signal(&cond_pro);
      pthread_mutex_unlock(&mutex);
      return true;
    } 

    ~BlockQueue(){

      pthread_mutex_destroy(&mutex);
      pthread_cond_destroy(&cond_pro);
      pthread_cond_destroy(&cond_cus);
    }              
};

void* productor(void* arg){
  BlockQueue* q=(BlockQueue*)arg;
  int i=0;
  while(1){
    q->Push(i);
    printf("Producer queue data:%d\n",i++);
  }
  return NULL;
}
void* customer(void* arg){
  BlockQueue* q=(BlockQueue*)arg;
  while(1){
    int data;
    q->Pop(&data);
    printf("Consumer out of line data:%d\n",data);
  }
  return NULL;
}

int main(){
  BlockQueue q;
  pthread_t ptid[4],ctid[4];
  int ret;
  for(int i=0;i<4;i++){
    ret=pthread_create(&ptid[i],NULL,productor,&q);
    if(ret!=0){
      printf("create thread error\n");
      return -1;
    }
  } 

  for(int i=0;i<4;i++){
    ret=pthread_create(&ctid[i],NULL,customer,&q);
    if(ret!=0){
      printf("create thread error\n");
      return -1;
    }
  }

  for(int i=0;i<4;i++){
    pthread_join(ptid[i],NULL);
    pthread_join(ctid[i],NULL);
  }
  return 0;
}


Partial results:

be careful:
The capacity defined here is 5, so normally it should be 5 times in the queue and 5 times out of the queue alternately. However, since the operations in cus and pro threads are non atomic operations, the previous unfinished operations may be interrupted during the rotation of time slice.

Topics: Linux Design Pattern Multithreading queue