[Linux] Multi-threaded POSIX semaphore

1. Concept

A semaphore, also known as a semaphore
, is essentially a counter used to describe the number of critical resources.

sem: 0 -> 1 -> 0
If there is only 1 critical resource, then set sem to 1. When critical resources are to be used, sem changes from 1 to 0. Others want to apply, but they cannot apply for the pending queue. When waiting for critical resources to be released, sem changes from 0 to 1 before reapplying for critical resources. This semaphore is called a binary semaphore, which is equivalent to a mutex
lock

Each thread, when accessing the corresponding resource, first applies for the semaphore. If the application is
successful, it means that the thread is allowed to use the resource . If the
application is unsuccessful, it means that the resource cannot be used at present.

2. The working mechanism of the semaphore

The semaphore mechanism is similar to watching a movie and buying tickets. It is a resource reservation mechanism
. If the semaphore is successfully applied, it is equivalent to booking a part of resources.

Judging whether the conditions are satisfied determines the follow-up behavior.
The semaphore is already a counter of resources. The successful application of the semaphore itself indicates that the resource is available. The
failure of the application for the semaphore itself indicates that the resource is unavailable.
The essence is to convert the judgment into a semaphore application

3. Know the interface

POSIX semaphore and system V semaphore have the same function, both are used for synchronous operation to achieve the purpose of accessing shared resources without conflict, but POSIX can be used for inter-thread synchronization


sem_init - initialize the semaphore

Enter man sem_init

sem: indicates the semaphore
pshared: 0 indicates shared between threads, non-zero indicates shared between processes
value: the initial value of the semaphore (how much the counter value is initialized to)

sem_destroy - destroy semaphore

Enter man sem_destroy

Destroy the initialized semaphore

sem_wait - apply for semaphore

Enter man sem_wait

Perform the operation of applying for the semaphore to reduce the value of the semaphore by 1

sem_post - release semaphore

Enter man sem_post

Perform the operation of releasing the semaphore to increase the value of the semaphore by 1

4. Production and consumption model based on ring queue

Principle analysis

The ring queue is actually simulated using an array

An extra space in the array is to solve the problem of judging full


If empty, thread and tail are in the same position


If it is full, the next position of tail is head


The producer pushes data to the tail , that is, the production
consumer pops data to the head, that is, consumption


Are producers and consumers concerned with the same resources?
Not the same, the producer cares about the space of the entire ring queue (whether the store is full of goods)
the consumer cares about the data, (whether there are goods in the store, buy them if there are goods)


When do head and tail access the same region?
There is a large table with a scale area of ​​0-12 o’clock like a clock, and a plate is placed in each scale.
Two people A and B enter the room at the same time and see the table. A puts an apple on the plate, and B takes the apple behind.
A and B agree: B cannot exceed A, and only one apple can be placed on a plate

When A and B start, when there are no apples on the table, or when the table is full of apples, they will visit the same plate, that is, the
ring queue is empty, or the ring queue is full, and they will visit the same area

When the queue is empty and points to the same location, there is a competitive relationship,
let the producer run first (only after the producer produces data, the consumer can get the data)

When the queue is full, point to the same location, there is a competitive relationship,
let the consumer run first (the producer can only produce after the consumer gets the data)


Producers care about space, and space itself is also a resource, so a semaphore sem_room should be defined for the producer , and its initial value is N P(sem_room) —— Apply for a space semaphore

If the producer's production data is in the current space, the corresponding data will be +1, so the consumer can get the data
V (sem_data) - the value of the data semaphore +1

Consumers care about data, the semaphore is sem_data, and its initial value is 0
P(sem_data) —— Apply for data semaphore

The consumer takes the data away, and the current space is idle, so the producer can put the data
V(sem_room) - the value of the space semaphore +1

the code

code analysis

First create a ringqueue class in ringqueue.hpp


Use new in the main function to create an rq queue.
In order to ensure that producers and consumers see the same resource, the parameter args of both callback functions is rq


The callback function of the productorRoutine uses the push of the queue rq to insert data into the queue, that is, production


ringqueue class

In the ringqueue class

When explaining the principle above, only the consumer cares about the data semaphore, and only the producer cares about the space semaphore

structure

Initialize the ring size and _cap (capacity) of the ring queue to N

0 means sharing between threads, initialize the data semaphore to 0, and initialize the space semaphore to the capacity of the entire ring queue
(for the initialization value of both, there are detailed explanations in the principle)

destruct

Since the semaphore is initialized at construction time, the semaphore needs to be destroyed

push - production

Before production, it is necessary to ensure that the conditions are met before production can be carried out, so P operation is required-apply for semaphore

When using a semaphore, there is no need to judge
because the semaphore is a counter. The essence is to transfer the ready situation of resources from within the critical area to outside the critical area.
It itself describes the number of critical resources, so there is no need to judge whether the critical resources meet the conditions after entering the critical area.


Producers and consumers may visit the same location, but they may visit different locations with high probability.
Therefore, producers and consumers must have their own subscripts to indicate their locations.


Continuously perform P operations to insert data in the space.
If there is no space, consumers need to consume (V operations) and take the data away.

When the tail reaches an additional space position, it is actually equivalent to returning to the beginning of the head again,
so use %= to simulate a ring queue


Encapsulate sem_wait and sem_post with the help of functions P and V.
When used again, only need to call PV to achieve

insert image description here

pop - consumption

Continuously perform P operations to remove data from the space, and when the space is idle,
producers are required to produce (V operations) and place data on the space

Code

Ringqueue.hpp


#include<iostream>
#include<vector>
#include<semaphore.h>//信号量头文件
static const int N=5;//设置环形队列的大小

template<class T>
class ringqueue
{
    
    
private:
  void P(sem_t &s)
  {
    
    
     sem_wait(&s); 
  }
  void V(sem_t&s)
  {
    
    
     sem_post(&s);
  }
public:
    ringqueue(int num=N)
    : _ring(num),_cap(num)
    {
    
    
        //信号量初始化
        sem_init(&_data_sem,0,0);
        sem_init(&_space_sem,0,num);
        _c_step=_p_step=0;//生产和消费下标都为0
    }
    ~ringqueue()
    {
    
    
        //销毁信号量
     sem_destroy(&_data_sem);
     sem_destroy(&_space_sem);
    }

    void push(const T&in)//生产
    {
    
    
      P(_space_sem);//P操作 申请信号量
      _ring[_p_step++]=in;//将数据放入生产位置
      _p_step%=_cap;
      V(_data_sem);//V操作 释放信号量
    }
    void pop(T*out)//消费 
    {
    
    
       P(_data_sem);//P操作
       *out=_ring[_c_step++];//将该位置的数据给与out
       _c_step%=_cap;
       V(_space_sem);//V操作
    }
private:
   int _c_step;//消费者位置下标
   int _p_step;//生产者位置下标
   std::vector<int> _ring;//充当环形队列
   int  _cap;//环形队列的容器大小
   sem_t _data_sem;//数据信号量
   sem_t _space_sem;//空间信号量 
};

makefile

ringqueue:main.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f ringqueue 

main.cc


#include"Ringqueue.hpp"
#include<pthread.h>
#include<unistd.h>
using namespace std;
void*consumerRoutine(void*args)
{
    
    
  ringqueue<int>*rq=(ringqueue<int>*)args;
  while(true)
  {
    
    
  int data=0;
  rq->pop(&data);//从队列取出数据 消费
  cout<<"consumer done:"<<data<<endl;
  sleep(1);
  }
}
void*productorRoutine(void*args) 
{
    
    
 ringqueue<int>*rq=(ringqueue<int>*)args;
 while(true)
 {
    
    
   int data=1;
   rq->push(data);//将数据插入队列中 生产
   cout<<"productor done:"<<data<<endl;
 }
}

int main()
{
    
    
   ringqueue<int>*rq=new ringqueue<int>();
   pthread_t c;//消费者
   pthread_t p;//生产者
   //创建线程
   pthread_create(&c,nullptr,consumerRoutine,rq);
     pthread_create(&p,nullptr,productorRoutine,rq);

     pthread_join(c,nullptr);
     pthread_join(p,nullptr);
    return 0;
}

Guess you like

Origin blog.csdn.net/qq_62939852/article/details/131754311