[Linux] Producer and consumer models, condition variables, semaphores

orange color

producer and consumer model

Producers and consumers are an important model in the operating system, which describes a waiting and notification mechanism.

For details, please view this article: Producer and Consumer Model

mutex lock

Mutex locks are a simple method to control thread operations on shared resources. In a sense, the mutex can be regarded as a global variable, which can be simply understood to mean that it can only be operated by one thread at a time.
Mutex locks have two states: locked and unlocked.

  • Only one thread can hold the mutex at a time.
  • The thread holding the mutex lock can operate on shared resources
  • If another thread wants to lock a mutex that is already locked, the thread will be suspended until the locked thread releases the mutex.
  • Mutex locks ensure that each thread operates on shared resources in order.
pthread_mutex_init (pthread_mutex_t *_mutex,const pthread_mutex_t *mutexatter)
初始化互斥锁函数
第一个参数mutex是只想要初始化的互斥锁的指针
第二个参数mutex是指向属性对象的指针,定义的是初始化互斥锁的属性,一般为NULL,即默认属性。
此外,也可以用宏PTHREAD_MUTEX_INTIALIZER 初始化静态分配的互斥锁。

pthread_mutex_destroy (pthread_mutex_t *mutex)
锁毁互斥锁函数
参数为指向互斥锁的指针
当这两个函数成功完成时返回0.否则返回错误编号指明错误。

int pthread_mutex_lock(pthread_mutex_t *mutex);
以阻塞的方式申请互斥锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
以非阻塞的方式申请互斥锁

pthread_mutex_unlock(pthread_mutex_t *mutex)
释放互斥锁
释放操作只能由占有该互斥锁的线程完成。

condition variable

Function analysis

Condition variables are not locks. Condition variables allow a thread to block or unblock after a certain condition is met.

/*
    条件变量的类型 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);
        - 等待,调用了该函数,线程会阻塞。

    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
        - 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。

    int pthread_cond_signal(pthread_cond_t *cond);
        - 唤醒一个或者多个等待的线程

    int pthread_cond_broadcast(pthread_cond_t *cond);
        - 唤醒所有的等待的线程
*/

Code example

Why should we introduce a condition variable in line 58? Because if you rely solely on mutex locks, if no node is produced and the node in the container is 0, then the consumer sub-thread will continue in an infinite loop, which is undoubtedly an extremely waste of resources. So a better approach is for consumers to notify producers to produce nodes when there are no nodes.
Line 32 can use pthread_cond_signal or pthread_cond_signal.
Line 58 pthread_cond_wait(&cond, &mutex). When the consumer sub-thread blocks here, line 23 pthread_mutex_lock(&mutex). Can the producer sub-thread still get the lock? If not, wouldn't it be a deadlock? In fact, this is not the case. When the consumer sub-thread is blocked here, wait will perform an unlocking operation on the mutex mutex lock so that the lock can be taken. But it should be noted that when wait is unblocked, the mutex lock will be added again, so there needs to be an unlocking operation below.

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

// 创建一个互斥量
pthread_mutex_t mutex;
// 创建条件变量
pthread_cond_t cond;

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

// 头结点
struct Node * head = NULL;

void * producer(void * arg) {
    
    

    // 不断的创建新的节点,添加到链表中
    while(1) {
    
    
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        //这里用的是头插法,把新生成的节点插到了链表的最前面
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;//成一个介于0到999之间的随机整数
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        
        // 只要生产了一个,就通知消费者消费
        pthread_cond_signal(&cond);

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

    return NULL;
}

void * customer(void * arg) {
    
    

    while(1) {
    
    
        pthread_mutex_lock(&mutex);
        // 保存头结点的指针
        struct Node * tmp = head;
        // 判断是否有数据
        if(head != NULL) {
    
    
            // 有数据
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
    
    
            // 没有数据,需要等待
            // 当这个函数调用阻塞的时候,会对互斥锁进行解锁,当不阻塞的,继续向下执行,会重新加锁。
            pthread_cond_wait(&cond, &mutex);
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {
    
    

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

    // 创建5个生产者线程,和5个消费者线程
    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)死循环主要是防止上面的子线程刚一创建好,下面就把互斥锁和条件变量给销毁了
    while(1) {
    
    
        sleep(10);
    }

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

	//这里调用pthread_exit()函数,使得主线程结束而子线程不会结束,且后面的return 0也不会执行
    pthread_exit(NULL);

    return 0;
}

operation result:

Insert image description here

1. In this model of consumers and producers, producers and consumers use the same lock. Because as long as multiple threads operate on the same piece of data, code synchronization needs to be ensured.


2. Question: If there is enough data in the container and the consumer does not execute pthread_cond_wait(&cond,&mutex)
, who will the pthread_cond_signal(&cond) signal in the producer notify, or where is the signal?
    Answer: Signal wakes up one or more sleeping threads. If there is enough data and the thread does not sleep, no processing will be done even if the signal is received. There is a saying on page 531 of "Liunx/UNIX System Programming Manual" that condition variables do not save status information, but are just a communication mechanism to transfer application status information. If no thread is waiting for the condition variable when the signal is sent, the problem will be nothing. If the thread waits for the condition variable thereafter, it can only be released from the blocking state when it receives the next signal of this variable again.


3. Question: When the consumer executes pthread_cond_wait and unlocks the mutex, will other consumer threads preemptively lock the mutex at this time, causing the producer to be unable to produce normally?
    Answer: Other consumers are also waiting in pthread_cond_wait

Signal amount

Function analysis

/*
    信号量的类型 sem_t
    int sem_init(sem_t *sem, int pshared, unsigned int value);
        - 初始化信号量
        - 参数:
            - sem : 信号量变量的地址
            - pshared : 0 用在线程间 ,非0 用在进程间
            - value : 信号量中的值

    int sem_destroy(sem_t *sem);
        - 释放资源

    int sem_wait(sem_t *sem);
        - 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞

    int sem_trywait(sem_t *sem);

    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    
    int sem_post(sem_t *sem);
        - 对信号量解锁,调用一次对信号量的值+1

    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);    //当csem信号量不为0时,减一;为0时,就堵塞在这里
        sem_post(&psem)
    }

*/

Code example

Here is the quote

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

// 创建一个互斥量
pthread_mutex_t mutex;
// 创建两个信号量
sem_t psem;
sem_t csem;

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

// 头结点
struct Node * head = NULL;

void * producer(void * arg) {
    
    

    // 不断的创建新的节点,添加到链表中
    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);
        // 保存头结点的指针
        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);

    // 创建5个生产者线程,和5个消费者线程
    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;
}

Insert image description here

Because the head insertion method is used, the last one added to the linked list is printed first.
The size of the semaphore determines the capacity of the container

Guess you like

Origin blog.csdn.net/mhyasadj/article/details/131115240