[Linux] プロデューサーおよびコンシューマー モデル、条件変数、セマフォ

オレンジ色

生産者と消費者のモデル

プロデューサとコンシューマは、待機および通知メカニズムを記述するオペレーティング システムの重要なモデルです。

詳細については、この記事を参照してください:プロデューサーとコンシューマー モデル

ミューテックスロック

ミューテックス ロックは、共有リソース上のスレッド操作を制御する簡単な方法です。ある意味、ミューテックスはグローバル変数とみなすことができます。これは、一度に 1 つのスレッドによってのみ操作できることを意味すると簡単に理解できます。
ミューテックス ロックには、ロックとロック解除の 2 つの状態があります。

  • 一度に 1 つのスレッドだけがミューテックスを保持できます。
  • ミューテックス ロックを保持しているスレッドは共有リソース上で動作できます
  • すでにロックされているミューテックスを別のスレッドがロックしようとした場合、そのスレッドは、ロックされたスレッドがミューテックスを解放するまで一時停止されます。
  • ミューテックス ロックにより、各スレッドが共有リソース上で順番に動作することが保証されます。
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)
释放互斥锁
释放操作只能由占有该互斥锁的线程完成。

条件変数

機能分析

条件変数はロックではありません。条件変数を使用すると、特定の条件が満たされた後にスレッドをブロックまたはブロック解除できます。

/*
    条件变量的类型 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);
        - 唤醒所有的等待的线程
*/

コード例

なぜ 58 行目に条件変数を導入する必要があるのでしょうか? なぜなら、ミューテックス ロックのみに依存する場合、ノードが生成されず、コンテナ内のノードが 0 の場合、コンシューマー サブスレッドは無限ループを続けることになり、これは間違いなくリソースの極度の無駄遣いとなります。したがって、より良いアプローチは、ノードがない場合にコンシューマーがプロデューサーにノードを生成するように通知することです。
32 行目は pthread_cond_signal または pthread_cond_signal を使用できます
58 行目 pthread_cond_wait(&cond, &mutex) ここでコンシューマ サブスレッドがブロックされると、23 行目 pthread_mutex_lock(&mutex). プロデューサ サブスレッドはまだロックを取得できますか? そうでないと行き詰まりではないでしょうか?実際にはそうではなく、コンシューマー サブスレッドがここでブロックされている場合、wait はロックを取得できるように、ミューテックス ミューテックス ロックのロック解除操作を実行します。ただし、wait が解除されると再度 mutex ロックが追加されるため、以下のロック解除操作が必要になることに注意してください。

#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;
}

操作結果:

ここに画像の説明を挿入します

1. コンシューマとプロデューサのこのモデルでは、プロデューサとコンシューマは同じロックを使用します。複数のスレッドが同じデータを操作する限り、コードの同期を確保する必要があるためです。


2. 質問: コンテナーに十分なデータがあり、コンシューマーが pthread_cond_wait(&cond,&mutex) を実行しない場合
、プロデューサーの pthread_cond_signal(&cond) シグナルは誰に通知しますか、またはシグナルはどこにありますか?
    回答: シグナルは 1 つまたは複数のスリープ状態のスレッドをウェイクアップします。十分なデータがありスレッドがスリープしていない場合は、シグナルを受信して​​も処理は行われません。『Liunx/UNIX システム プログラミング マニュアル』の 531 ページには、条件変数はステータス情報を保存するものではなく、アプリケーションのステータス情報を転送するための通信メカニズムにすぎないという記述があります。シグナルの送信時に条件変数を待っているスレッドが存在しない場合、問題は発生しません。その後スレッドが条件変数を待っている場合、この変数の次のシグナルを再度受信したときにのみブロック状態を解除できます。


3. 質問: コンシューマーが pthread_cond_wait を実行してミューテックスのロックを解除すると、この時点で他のコンシューマー スレッドが先制的にミューテックスをロックし、プロデューサーが正常に生成できなくなりますか?
    回答: 他のコンシューマも pthread_cond_wait で待機しています

信号量

機能分析

/*
    信号量的类型 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)
    }

*/

コード例

ここに引用があります

#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;
}

ここに画像の説明を挿入します

先頭挿入方式を採用しているため、リンクリストに最後に追加されたものが最初に印刷されます。
セマフォのサイズによってコンテナの容量が決まります

おすすめ

転載: blog.csdn.net/mhyasadj/article/details/131115240