Interview site -- trap of conditional variables, producer and consumer model

Posted by dajawu on Wed, 16 Feb 2022 16:39:36 +0100

I recommend a free open course of zero sound college. Personally, I think the teacher speaks well. I share it with you: Linux, Nginx, ZeroMQ, MySQL, Redis, fastdfs, MongoDB, ZK, streaming media, CDN, P2P, K8S, Docker, TCP/IP, collaboration, DPDK and other technical contents, learn immediately

1, Use of conditional variables

API s related to conditional variables under Linux are as follows:
pthread_cond_init: used to initialize condition variables.
pthread_ cond_ Destroy: destroy condition variables.
pthread_cond_broadcast: wake up all threads waiting for target condition variables by broadcasting.
pthread_cond_wait: used to wait for the target condition variable. The mutex parameter (locked mutex) needs to be passed in when the function is called. When the function is executed, first put the calling thread into the request queue of the condition variable, and then unlock the mutex. When the function returns to 0 successfully, it indicates that the mutex has been robbed again, and the mutex will be locked again, that is, there will be an unlocking and locking operation inside the function.

The basic usage of condition variables is as follows:

// 1. Method of using conditional variables by producers
......
pthread_mutex_lock(&mutex);
// Here, a task will be generated for the consumer thread to process
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond); // Wake up a consumer thread processing
......

// 2. Method of using conditional variable by consumer thread
......
pthread_mutex_lock(&mutex);
while(Whether the target conditions executed by the thread are met){
    pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
......

2, Research on conditional variables and traps

2.1 locking of conditional variables

1.pthread_ cond_ What did wait & cond, & mutex) do?
A: when the function is executed, the thread will be placed in the request queue of the condition variable, and the internal unlocking thread will wait to be pthreaded_ cond_ Broadcast signal wake-up or pthread_ cond_ The signal signal wakes up. After waking up, it competes for the lock. If it competes for the mutex lock, it will be locked internally again.

2. Why should the consumer thread be locked before use?
A: because of multi-threaded access, in order to avoid resource competition, it is necessary to lock so that each thread can access public resources mutually exclusive.

3.pthread_ cond_ Why should wait unlock?
A: when using while to judge, if the execution conditions are met, the thread will call pthread_cond_wait blocks itself. At this time, it still holds the lock. If it does not unlock, other threads will not be able to access public resources. Specific to pthread_ cond_ The internal implementation of wait, when pthread_ cond_ When the wait is blocked by the calling thread, pthread_cond_wait automatically releases the mutex.

4. Why put the calling thread into the request queue of the condition variable and then unlock it?
A: threads execute concurrently. If the mutex is released before placing the calling thread a on the waiting queue, it means that other threads, such as thread B, can obtain the mutex to access public resources. At this time, the waiting condition of thread a changes, but it is not placed on the waiting queue, resulting in a ignoring the signal that the waiting condition is satisfied. If pthread is called on thread a_ cond_ From the start of wait to the process of putting a in the waiting queue, they all hold mutexes. If other threads cannot get mutexes, they cannot change public resources.

5. Why pthread_ cond_ The wait function needs to be locked at the end?
A: after placing the thread in the request queue of the condition variable, unlock it and wait to be awakened. If the mutex lock is successfully competed and locked again, the consumer thread will obtain the consumption right of public resources.

2.2 false awakening of consumption thread

Why use while instead of if to judge the condition of thread execution?

A: Generally speaking, when multi-threaded resources compete, the consumer thread judges whether the resources are available or not, and then calls pthread_cond_wait, if the producer thread judges that the resource is available, it calls pthread_cond_signal sends a resource availability signal.

However, in pthread_ cond_ After the wait is successful, can the consumer thread consume resources? The answer is No. if there are two or more threads waiting for this resource at the same time, the resource may have been used after the wait returns, so it is called false wake-up if it is awakened but does not obtain the right to consume.

To be more specific, it is possible that multiple threads are waiting for the signal that the resource is available. After the signal is sent, only one resource is available, but there are two consumer threads A and B waiting. Thread B is relatively fast, obtains the mutex, then locks, consumes resources, and then unlocks. Then A obtains the mutex, but A goes back and finds that the resource has been used, It has two choices: one is to access nonexistent resources, and the other is to continue to wait. The condition for continuing to wait is to use while, or pthread if_ cond_ After the wait returns, it will be executed in sequence.

Therefore, in the case of multiple consumers, you should use while instead of if. If there is only one consumer, you can use if.

3, Producer condition based model and consumer condition based model

The article is written here. I believe you have a good understanding of conditional variables. Next, let's directly use conditional variables to implement a basic producer and consumer model. The c + + code is as follows:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <list>

using namespace std;

// task
class task {
public:
    task(int id){
        this->taskId = id;
    }

    void doTask(){
        cout << "  dotask id = " << taskId << endl;
    }

private:
    int taskId;
};

list<task*> taskList;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// Producer thread
void *producerThreadFunc(void *param){
    int taskId = 0;
    task *t = nullptr;

    while (true){
        t = new task(taskId);

        pthread_mutex_lock(&mutex);
        taskList.emplace_back(t);
        cout << "Produce task, id = " << taskId << endl;
        pthread_mutex_unlock(&mutex);

        pthread_cond_signal(&cond); // Wake up a consumer thread processing
        taskId++;

        sleep(1);
    }

    return nullptr;
}

// Consumer thread
void *consumerThreadFunc(void *param){
    task *t = nullptr;

    while (true){
        pthread_mutex_lock(&mutex);

        while(taskList.empty()){
            pthread_cond_wait(&cond, &mutex);
        }

        t = taskList.front();
        taskList.pop_front();
        pthread_mutex_unlock(&mutex);

        cout << "ThreadID = " << pthread_self();
        t->doTask();
        delete t;
    }

    return nullptr;
}

int main(){
    pthread_t producerThread;
    pthread_t consumerThread[10]; // Create 10 consumption threads

    pthread_create(&producerThread, NULL, producerThreadFunc, NULL);
    for(int i = 0; i < 10; i++){
        pthread_create(&consumerThread[i], NULL, consumerThreadFunc, NULL);
    }

    pthread_join(producerThread, NULL);
    for(int i = 0; i < 10; ++i){
        pthread_join(consumerThread[i], NULL);
    }

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    return 0;
}

The results are as follows:

Reference blogs are as follows:
Detailed explanation of the latest version of Web server project - 09 log system (Part 1): https://mp.weixin.qq.com/s/IWAlPzVDkR2ZRI5iirEfCg

Topics: C++ Linux Interview Operating System