In the previous blog , the producer-consumer model based on blocking queues , I introduced what is the producer-consumer model. If you haven't understood yet, you can check the link. This blog also implements a producer-consumer model, but in this blog I will implement a production-consumption model based on a circular queue.
POSIX semaphore
It should be noted here that we are using POSIX-based semaphores this time. Don’t mistake it for SystemV semaphores. Although the two things are different, their functions are the same. They are all used to ensure synchronization. , To achieve the purpose of conflict-free access to shared resources, but POSIX semaphores can be used for thread synchronization.
The semaphore is essentially a counter that records the number of critical resources . It has two very important operations. One is an addition operation, also known as the v operation. This operation will increase the semaphore by one, and the other operation will be a subtraction. Also known as the p operation, this operation will reduce the semaphore value by one. Next, let's look at the operation function of the semaphore.
Here we need to pay special attention: Although it is just a counter, it cannot be replaced by a global variable, because the PV operation of the semaphore is atomic, and the global variable defined by ourselves cannot guarantee atomicity.
Semaphore related operations
- Initialize the semaphore
#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
sem: semaphore
//pshared:0 means shared between threads, non-zero means shared between processes
//value: initial value of semaphore
- Destroy semaphore
int sem_destroy (sem_t * sem);
- Wait for semaphore
int sem_wait(sem_t *sem);
Function: When waiting for the semaphore, the value of the semaphore will be reduced by one, corresponding to the P operation
- Semaphore
int sem_post(sem_t *sem);
Function: After the resource is used, return the resource and add one to the value of the semaphore, corresponding to the V operation
Production and consumption model based on circular queue
In the previous section, we implemented a blocking queue, whose space can be dynamically allocated.
Now we will implement this model again based on a fixed-size circular queue. Every time a producer produces a batch of data in the queue, the consumer can read a batch of data from the queue, so at this time the circular queue becomes a trading place, p represents the producer, c represents the consumer, when the producer Consumers can take the data away after pushing to the queue.
There are several principles to follow:
- Consumer behavior cannot precede production behavior
- Production behavior cannot exceed consumption behavior
- Although the empty/full status is difficult to determine, they must be at the same point
- When the queue is empty, there can only be production behavior, and when it is full, there can only be consumption behavior
The biggest problem at this time is: the actual state and end state of the ring structure are the same, it is difficult to judge whether it is empty or not full, so we can judge by adding a counter or a flag bit, or we can reserve an empty position as Full state.
We now have a semaphore counter, so we use the array moni circular queue, and use modular arithmetic to simulate the characteristics of the ring. Can easily realize the synchronization process between multiple threads.
The operation of the producer (P) includes taking vacancies and increasing data
P (sem_space) the number of vacancies--
V (sem_data) the number of data + +
Consumer (C) operations include fetching data to increase space
P (sem_data) Number of data--
V (sem_space) Number of vacancies + +
Without further ado, look at the following code to understand:
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#define NUM 16
using namespace std;
class RingQueue{
private:
vector<int> q; //模拟环形对列数组
int cap; //资源的数量
sem_t data_sem; //数据
sem_t space_sem; //空位
int consume_step; //消费者所在位置
int product_step; //生产者所在位置
public:
RingQueue(int _cap=NUM):q(_cap),cap(_cap)
{
sem_init(&data_sem,0,0);
sem_init(&space_sem,0,cap);
consume_step=0;
product_step=0;
}
void PutData(const int &data) //生产者行为
{
sem_wait(&space_sem); //等待信号对应减一
q[product_step]=data;
product_step++;
product_step%=cap; //保证不越界
sem_post(&data_sem); //发布信号对应加一
}
void GetData(int &data) //消费者行为
{
sem_wait(&data_sem); //等待数据
data=q[consume_step];
consume_step++;
consume_step%=cap;
sem_post(&space_sem); //发布空位
}
~RingQueue()
{
sem_destroy(&data_sem);
sem_destroy(&space_sem);
}
};
void *consume(void *arg)
{
RingQueue *rq=(RingQueue*)arg;
int data;
while(1)
{
rq->GetData(data);
cout<<"Consume data done:"<<data<<endl;
sleep(1);
}
}
void *product(void *arg)
{
RingQueue *rq=(RingQueue*)arg;
srand((unsigned long)time(NULL));
while(1)
{
int data=rand()%100+1;
rq->PutData(data);
cout<<"Product data done:"<<data<<endl;
//sleep(1);
}
}
int main()
{
RingQueue rq;
pthread_t c,p;
pthread_create(&c,NULL,consume,(void*)&rq);
pthread_create(&p,NULL,product,(void*)&rq);
pthread_join(c,NULL);
pthread_join(p,NULL);
return 0;
}
I believe the code here is also easy to understand.
We have now implemented a producer-consumer model based on blocking queues and semaphores. It is actually not difficult for them to implement them. The point is that readers need to understand the solution to the problem and understand the role and significance of the model itself.