[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
title | author | Reference place |
---|---|---|
Linux high concurrency server | Niuke network | Throughout the full text, based on this |
Producer consumer model: theoretical explanation and Implementation (C + +) | HOracle | Production 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
- What is the producer consumer model
- Why use the producer consumer model
- Characteristics of producer consumption model
- Advantages of producer consumer model
- Application scenario of producer consumer model
- 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; }