Introduction to multi-threading in Linux | Thread synchronization | Production and consumption model

Article directory

1. Multi-thread synchronization

1. Concept

2. Condition variables

2.1 Concept of condition variables

2.2 Condition variable interface

1. Condition variable initialization

2. Wait for the conditions to be met

3. Wake up and wait

3. Destroy condition variables

2.3 Condition variable demo

2. Production and consumption model

1. Production and consumption model

2. Producer-consumer model based on BlockQueue

3. Implement a production and consumption model based on C++ using condition variables and mutex locks

4. Signal amount

1. Semaphore concept

2.Semaphore interface

1.Initialize the semaphore

2. Wait for the semaphore (P operation--)

3. Release a semaphore (V operation++)

4. Destroy the semaphore

5. Ring producer-consumer model



When a thread accesses a variable exclusively, it finds that it may be suspended before other threads change state.

For example, if a thread accesses a queue and finds that the queue is empty, it can only wait. Until other threads add a node to the queue, this situation requires the use of condition variables.

1. Multi-thread synchronization

1. Concept

Under the premise of ensuring data security, allowing threads to access critical resources in a specific order , thereby effectively avoiding the starvation problem , is called synchronization.

2. Condition variables

2.1 Concept of condition variables

Condition variables are a means of thread synchronization. If there is only one thread and the condition is not satisfied, it will not be satisfied if it keeps waiting. Therefore, there must be a thread that changes the shared variable through certain operations so that the original unsatisfied condition changes. is satisfied, and the thread waiting on the condition variable is notified friendly.

Conditional variables will not be satisfied for no reason, and will inevitably involve changes in shared data, so they must be protected by locks. Without locks, shared data cannot be safely obtained and modified.

2.2 Condition variable interface

1. Condition variable initialization

int pthread_cond_init(pthread_cond_t * restrict cond,const pthread_condattr_t * restrict attr);

参数:cond 要初始化的条件变量
      attr:NULL

2. Wait for the conditions to be met

int pthread_cond_wait(pthread_cond_t * restrict cond,pthread_mutex_t * restrict mutex);

参数:
    cond: 要在这个条件变量上等待
    mutex:互斥量,等待的时候要释放掉这个锁

3. Wake up and wait

int pthread_cond_broadcast(pthread_cond_t * cond);
int pthread_cond_signal(pthread_cond_t * cond);

3. Destroy condition variables

int pthread_cond_destroy(pthread_cond_t * cond);

2.3 Condition variable demo

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<cstdio>
#include<string>
using namespace std;



const int num = 5;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;



void *active(void *args)
{
    string name = static_cast<const char*>(args);
    while(true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);// 在调用的时候,会自动释放锁
        cout<<name<<" activing..."<<endl;
        pthread_mutex_unlock(&mutex);

    }

}
int main()
{
    pthread_t tids[num];
    for(int i = 0; i<num;i++)
    {
        char * name = new char[32];
        snprintf(name,32,"thread-%d",i+1);

        pthread_create(tids+i,nullptr,active,name);
    }


    sleep(3);

    while(true)
    {
        cout<<"main thread wakeup thread"<<endl;
        pthread_cond_signal(&cond);
        sleep(1);
    }


    for(int i = 0; i<num;i++)
    {
        pthread_join(tids[i],nullptr);
    }

}

2. Production and consumption model

1. Production and consumption model

The producer-consumer pattern solves the strong coupling problem between producers and consumers through a container. Producers and consumers do not communicate directly with each other, but communicate through blocking queues. Therefore, after the producers produce the data, they do not need to wait for the consumers to process it, but directly throw it to the blocking queue. The consumers do not ask the producers for data, but Take it directly from the blocking queue. The blocking queue is equivalent to a buffer, balancing the processing capabilities of producers and consumers. This blocking queue is used to decouple producers and consumers.

2. Producer-consumer model based on BlockQueue

In multi-threaded programming, blocking queue (Blocking Queue) is a data structure commonly used to implement the producer and consumer model. The difference from an ordinary queue is that when the queue is empty, the operation of obtaining elements from the queue will be blocked until an element is placed in the queue; when the queue is full, the operation of storing elements Block until an element is taken out of the queue ( the above operations are based on different threads, and the thread will be blocked when operating on the blocked queue process.

3. Implement a production and consumption model based on C++ using condition variables and mutex locks

Requirements: Use condition variables and mutex locks to implement a production and consumption model. The production and consumption model is a queue, as shown in the figure above. Here, the queue in stl is used to implement single production and single consumption. One thread is responsible for production and one thread is responsible for consumption. These two threads need to access the same queue, so a lock is required. When the thread hangs, a signal is also needed to tell the thread that the conditions are now met, so two condition variables are used to notify the production and consumption threads to change to the ready state respectively.

Implementation: Encapsulate this model into a class. When two threads access it, if the queue is not full, they can produce and push data to the queue. If the data in the queue is not empty, they can consume and pop data from the queue. Therefore, two interfaces, push and pop, are needed. After successful debugging, multi-production and multi-consumption are finally implemented. The implementation code is as follows:

//blockqueue.hpp 声明,方法,定义在一个文件中

const int gcap = 5;
template<class T>
class blockQueue
{

public:
    //构造
    blockQueue(const int cap = gcap)
    :_cap(cap),
    {
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_consumerCond,nullptr);
        pthread_cond_init(&_productorCond,nullptr);
    }

    bool isFull() {return _q.size() == _cap;}
    bool isEmpty() { return _q.empty()};

    //将数据塞进队列 生产
    void push(const T & in)
    {
      pthread_mutex_lock(&_mutex);
      //注意这里不要用if,可能会误唤醒
        while(isFull())
        {
            //在当前的条件下休眠,就注定了要释放锁,让别的线程去竞争锁
            //休眠,就是被os切走了,醒来之后又要重新申请锁
            pthread_cond_wait(&_productorCond,&_mutex);  
        }
        
        //如果没满,就让他继续生产
        _q.push(in);
        
        //生产之后,让消费者来消费,唤醒消费的线程 再释放自己手中的锁
        pthread_cond_signal(&_consumerCond);
        pthread_mutex_unlock(&_mutex);
    }

    //队列非空 消费  
      void pop()
    {
        pthread_mutex_lock(&_mutex);
        //判断队列是否为空
        while(isEmpty())
        {
            //空的话,在当前条件下休眠
            pthread_cond_wait(&_consumerCond,&_mutex);
        }

        //非空 开始消费 并且唤醒生产者 可以生产了
        pthread_cond_signal(&_productorCond);
        pthread_mutex_unlock(&_mutex);
    }

    //析构
    ~blockQueue()
    {
        //释放锁和两个信号量 队列是一个临时变量可以不用在这里释放
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&_consumerCond);
        pthread_cond_destroy(&_productorCond);
    }

private:
    std::queue<T> _q;
    int _cap; //队列中的容量
    pthread_mutex_t _mutex;
    pthread_cond_t _consumerCond; //消费者对应的条件变量,如果队列中数据为空,wait
    pthread_cond_t _productorCond; //生产者对应的条件变量,如果队列中数据为满,wait
};

4. Signal amount

1. Semaphore concept

        POSIX semaphores have the same function as System V semaphores. They are both used for synchronization operations to achieve conflict-free access to shared resources. But POSIX can be used for inter-thread synchronization. The essence of semaphore is to describe the quantity in critical resources.

        sem = 1, there are only two states of 0/1, which are mutex locks.

        Multiple semaphores: Each thread first applies for a semaphore when accessing the corresponding resource. If the application is successful, it means that the resource is now available. The application failed and is currently inaccessible.

2.Semaphore interface

#include<semaphore.h>

1.Initialize the semaphore

int sem_init(sem_t * sem, int pshared ,unsiged int value);

参数:pshared 0 表示线程间共享,非0 表示进程间共享
     value:信号量初始值

2. Wait for the semaphore (P operation--)

int sem_wait(sem_t * sem);

3. Release a semaphore (V operation++)

int sem_post(sem_t * sem);

4. Destroy the semaphore

int sem_destroy(sem_t * sem);

5. Ring producer-consumer model 

       The circular queue is simulated using an array, and the % operation is used to simulate the circular characteristics. Game rules must be ensured when it is empty/full, and concurrency must be guaranteed when it is neither empty nor full.

The start state and end state of the ring structure are the same. It is not easy to judge whether it is empty or full, so it is judged by adding a counter or flag bit. In addition, you can also reserve an empty position as a full state.

But now that we have the semaphore counter, we can perform the synchronization process between multiple threads.

The producer cares about whether the space is full, and the consumer cares about whether there is data. As long as the ring queue accesses different areas, production and consumption activities can be carried out at the same time.

Requirements : The production and consumption model is a queue , simulated using an array, and requires two threads at the same time , a production thread and a consumption thread. There are three types of relationships to be maintained : relationships between producers and producers, consumers and consumers, and relationships between producers and consumers. Among them, producer and producer need to be mutually exclusive. Consumer and consumer are equally mutually exclusive. Producers and consumers need to produce before consuming, so they need to be synchronized. At the same time, accessing the same queue (shared resource) requires a mutually exclusive relationship.

Implementation: Encapsulate the queue into a class and use array simulation to implement it. The interfaces that the class needs to expose are push and pop , which implement p operations and v operations. At the same time, you need to know the size of this queue and define two semaphores, one that consumers care about and one that producers care about . After successfully applying for a semaphore, you must also know the positions in the corresponding queues of production and consumption at this moment , that is, which area is specifically maintained. That is, two subscripts. Apply for the resources you care about and V each other’s resources

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);
            
            //刚开始都为0
            _c_step = _p_step = 0;
        }

        void push(const T &in)
        {

           //申请
           P(_space_sem);
           _ring[_p_step++] = in;
           _p_step %= _cap;
           V(_data_sem);
        }
        
        void pop(T * out)
        {

            P(_data_sem);
            *out = _ring[_c_step++];
            _c_step &= _cap;
            V(_spcae_sem);
        }

        ~RingQueue()
        {
            sem_destroy(&_data_sem);
            sem_destroy(&_space_sem);
        }
    private:
        std::vector<T> _ring;
        int _cap;
        sem_t _data_sem;
        sem_t _space_sem;
        int _c_step;
        int _p_step;
};

Guess you like

Origin blog.csdn.net/jolly0514/article/details/132688039