[Linux high concurrency server] producer and consumer model

Keywords: Linux Operation & Maintenance Back-end server Project

[Linux high concurrency server] producer and consumer model

Article overview

This article is a personal note of Niuke C + + project course: Linux high concurrency server, which records some knowledge points of producer and consumer model

Author information

NEFU 2020 zsl
ID: fishingrod / fishing rod
Email: 851892190@qq.com
You are welcome to quote this blog. When quoting, place the original link and basic information of the author in a prominent position

reference material

Thank you for the excellent materials left by the predecessors. I have learned a lot from them. I dare to quote them. If there is any offence, you can send a private letter or point it out at the bottom of the comment area

titleauthorReference place
Linux high concurrency serverNiuke networkThroughout the full text, based on this
Producer consumer model: theoretical explanation and Implementation (C + +)HOracleProduction and consumer model concepts

Body part

Originally, acwing heard about producer and consumer models when he was studying thrift framework (an RPC framework), but he was confused when he didn't learn these things at that time.

Producer and consumer model

Producer consumer model: theoretical explanation and Implementation (C + +)
This blog is very good. I strongly recommend that you take a look and answer the following questions


The picture is taken from the blog of HOcrale boss

  1. What is the producer consumer model
  2. Why use the producer consumer model
  3. Characteristics of producer consumption model
  4. Advantages of producer consumer model
  5. Application scenario of producer consumer model
  6. Why set a buffer between producers and consumers (what's the problem if you don't set it)

CODE

The following codes do not deal with the case that the container is full, but only deal with the case that it is out of stock

Pseudo code

// Create a mutex
pthread_mutex_t mutex;

//Container (buffer) Pool

void * producer(void * arg) {
    while(1) {
        //Lock
        pthread_mutex_lock(&mutex);
        //Fill the container
		
		//Unlock
        pthread_mutex_unlock(&mutex);
    }
	return NULL;
}

void * customer(void * arg) {

    while(1) {
      	//Lock
      	pthread_mutex_lock(&mutex);

        // See if there are any goods
        if(head != NULL) {// in stock
            //consumption
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {// Out of stock          
            // Unlock
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

Version one

/*
    Producer consumer model (rough version)
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// Create a mutex
pthread_mutex_t mutex;

struct Node{
    int num;
    struct Node *next;
};

// Head node
struct Node * head = NULL;

void * producer(void * arg) {

    // Constantly create new nodes and add them to the linked list
    while(1) {
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        pthread_mutex_unlock(&mutex);
        usleep(100);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        pthread_mutex_lock(&mutex);
        // Save pointer to header node
        struct Node * tmp = head;

        // Determine whether there is data
        if(head != NULL) {
            // Have data
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // no data
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);

    // Create 5 producer threads and 5 consumer threads
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);

    pthread_exit(NULL);

    return 0;
}

Improvement and optimization

Conditional variable optimization

In the code in version 1, the code of the consumer model is as follows

void * customer(void * arg) {

    while(1) {
        pthread_mutex_lock(&mutex);
        // Save pointer to header node
        struct Node * tmp = head;

        // Determine whether there is data
        if(head != NULL) {
            // Have data
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // no data
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

It can be found that if there is no data, consumers just unlock it.
This may occur. The lock is always unlocked and taken back by the consumer, resulting in many unnecessary judgments.
To optimize this, we consider the following options:
If the consumer finds that there is no data, it will block there and release the lock to the producer for production, so as to avoid redundant judgment. This is what conditional variables do.

When the container is empty, tell the consumer thread that you have to wait until the producer thread produces something and release the lock to him
For producer threads: after you produce the product, tell the consumer that the thread can start consumption (wake up)

/*
    Type of conditional variable pthread_cond_t
    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
    int pthread_cond_destroy(pthread_cond_t *cond);
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
        - Wait. If this function is called, the thread will block.
    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
        - How long to wait? After calling this function, the thread will block until the specified time ends.
    int pthread_cond_signal(pthread_cond_t *cond);
        - Wake up one or more waiting threads
    int pthread_cond_broadcast(pthread_cond_t *cond);
        - Wake up all waiting threads
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// Create a mutex
pthread_mutex_t mutex;
// Create condition variable
pthread_cond_t cond;

struct Node{
    int num;
    struct Node *next;
};

// Head node
struct Node * head = NULL;

void * producer(void * arg) {

    // Constantly create new nodes and add them to the linked list
    while(1) {
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        
        // As long as one is produced, consumers will be notified of consumption
        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&mutex);
        usleep(100);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        pthread_mutex_lock(&mutex);
        // Save pointer to header node
        struct Node * tmp = head;
        // Determine whether there is data
        if(head != NULL) {
            // Have data
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // No data, need to wait
            // When this function call is blocked, the mutex lock will be unlocked. When it is not blocked, continue to execute downward and re lock it.
            pthread_cond_wait(&cond, &mutex);
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    // Create 5 producer threads and 5 consumer threads
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

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

    pthread_exit(NULL);

    return 0;
}

Semaphore implementation

Semaphores should be a gadget for marking mutexes

reference resources This problem is unknown Answer under
The author ID is: fat man
I don't know why I can't set the answer link directly, so it's the only way. Here's my personal understanding

The two basic requirements of multithreading concurrency are synchronization and waiting
Synchronization: multiple threads share the same resource and access and use it at the same time. Synchronization and coordination are required between threads, which can be solved by using mutex lock

Wait: if there is a dependency between multiple threads, you need to wait. For example, in the producer consumer model, consumers need to wait for the producer's output data to consume.

The simplest way to solve the problem of waiting is to poll and check the situation at regular intervals
The code in version 1 is polling, but the time interval is not set. The disadvantage of polling is that it may or make a lot of repeated judgments.
In order to solve this problem, we use conditional variables for optimization. Let the consumer thread block there when there is no data and wait for the producer to produce.

Next, let's talk about semaphores. We can think that semaphores = mutex + conditional scalars are a mechanism that can realize synchronization and waiting. We can interpret semaphores as the number of currently available public resources. Imagine a parking lot. The number of parking spaces is the maximum capacity. The current number of empty parking spaces is the currently available public resources. One semaphore in is - 1 and one semaphore out is + 1. If there is no empty parking space, the semaphore is 0 and the thread is blocked.

/*
    Semaphore type sem_t
    int sem_init(sem_t *sem, int pshared, unsigned int value);
        - Initialization semaphore
        - Parameters:
            - sem : Address of semaphore variable
            - pshared : 0 Used between processes, non-0 used between processes
            - value : Value in semaphore

    int sem_destroy(sem_t *sem);
        - Release resources

    int sem_wait(sem_t *sem);
        - Lock the semaphore and call the semaphore value of - 1 once. If the value is 0, it will be blocked

    int sem_trywait(sem_t *sem);

    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    int sem_post(sem_t *sem);
        - Unlock the semaphore and call the value of semaphore + 1 once

    int sem_getvalue(sem_t *sem, int *sval);

    sem_t psem;
    sem_t csem;
    init(psem, 0, 8);
    init(csem, 0, 0);

    producer() {
        sem_wait(&psem);
        sem_post(&csem)
    }

    customer() {
        sem_wait(&csem);
        sem_post(&psem)
    }

*/

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <semaphore.h>

// Create a mutex
pthread_mutex_t mutex;
// Create two semaphores
sem_t psem;
sem_t csem;

struct Node{
    int num;
    struct Node *next;
};

// Head node
struct Node * head = NULL;

void * producer(void * arg) {

    // Constantly create new nodes and add them to the linked list
    while(1) {
        sem_wait(&psem);
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        pthread_mutex_unlock(&mutex);
        sem_post(&csem);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        sem_wait(&csem);
        pthread_mutex_lock(&mutex);
        // Save pointer to header node
        struct Node * tmp = head;
        head = head->next;
        printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
        free(tmp);
        pthread_mutex_unlock(&mutex);
        sem_post(&psem);
       
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);
    sem_init(&psem, 0, 8);
    sem_init(&csem, 0, 0);

    // Create 5 producer threads and 5 consumer threads
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);

    pthread_exit(NULL);

    return 0;
}

Posted by findshorty on Sun, 31 Oct 2021 00:48:23 -0700