IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 使用信号量实现生产者消费者|C语言 -> 正文阅读

[C++知识库]使用信号量实现生产者消费者|C语言


本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

Intro

  • 本文为多生产者多消费者的C语言实现
  • 生产者与消费者针对链表的头结点进行操作
  • 使用的现场同步方法为信号量
  • 本文侧重于理解条件变量的使用
  • 为保证代码简洁,本文代码没有对函数的返回值进行检查,聪明的你一定一定知道如何检查这个小细节(?????)? ??
  • 关于互斥锁+条件变量的多生产者多消费者模型实现请参考我的这篇博客

analysis

多生产者多消费者有多个线程,当根据信号量定义的资源数不同,所对应的情况也有所不同,所以在使用信号量去实现多生产者多消费者模型要处理好资源数与多线程之间的关系,在拥有多个资源数时可以配合互斥锁来约束多线程对共享资源的访问

单生产者单消费者

  • 限于篇幅,本篇博客仅简单提及此模型,不展示具体代码
  • 对于单生产者单消费者,资源数只能为1,每次唤醒一个线程,情况并不复杂
  • 若资源数大于1,实际运行就会产生段错误(读写了不该访问的地址)
    在这里插入图片描述

多生产者多消费者

资源数为1

sem_init(&psem, 0, 1);//意义为初始化生产者信号量,用于处理线程,资源数为1
sem_init(&csem, 0, 0);//意义为初始化消费者信号量,用于处理线程,资源数为0

如此定义的好处为:

  • 使生产者先工作

  • 由于资源数为1,程序运行中只有一个线程可被唤醒,不会出现多个线程同时访问共享资源的的情况

  • 无需添加互斥锁对共享资源进行保护

  • SHOW ME THE CODE

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

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

struct Node *head = NULL;
sem_t psem;//生产者信号量
sem_t csem;//消费者信号量


void *producer(void *arg);//生产者函数
void *consumer(void *arg);//消费者函数

int main(int argc, char **argv)
{
    pthread_t ptid[5];//生产者线程
    pthread_t ctid[5];//消费者线程

    sem_init(&psem, 0, 1);//初始化生产者信号量,生产者线程拥有1个信号灯
    sem_init(&csem, 0, 0);//初始化消费者信号量,消费者线程拥有0个信号灯
    //如此这般的意义是使生产者线程先运行

    for(int i = 0; i < 5; i++){//初始化线程
        pthread_create(&ptid[i], NULL, producer, NULL);
        pthread_create(&ctid[i], NULL, consumer, NULL);
    }
    
    sem_destroy(&psem);//反初始化信号量
    sem_destroy(&csem);
    
    for(int i = 0; i < 5; i++){//回收线程
        pthread_join(ptid[i], NULL);
        pthread_join(ctid[i], NULL);
    }

    return 0;
}

void *producer(void *arg)
{
    while(1){
        sem_wait(&psem);//生产者拿一个信号灯
        struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));//创建一个新节点
        pnew -> number = rand() % 100;//初始化
        pnew -> next = head;//链接节点
        head = pnew;//头结点前移
        printf("Producer: number = %d, tid = %ld\n", pnew -> number, pthread_self());

        sem_post(&csem);//通知消费者消费
        sleep(rand() % 3);
    }

    return NULL;
}

void *consumer(void *arg)
{
    while(1){
        sem_wait(&csem);
        struct Node *pnode = head;//生产者先运行确保此处节点挪动逻辑不会出错
        printf("Consumer: number = %d, tid = %ld\n", pnode -> number, pthread_self());
        head = pnode -> next;
        free(pnode);

        sem_post(&psem);//通知生产者生产

        sleep(rand() % 3);
    }

    return NULL;
}

资源数大于1

sem_init(&psem, 0, 5);//意义为初始化生产者信号量,用于处理线程,资源数为5
sem_init(&csem, 0, 0);//意义为初始化消费者信号量,用于处理线程,资源数为0

如此定义的好处为:

  • 使生产者先工作

  • 效率更高

  • 由于资源数为5,程序运行中有多个线程可被唤醒:

    • 多个生产者同时生产
    • 多个消费者同时消费
    • 生产者和消费者同时运行
  • 会出现多个线程同时访问共享资源的的情况

  • 需添加互斥锁对共享资源进行保护

  • SHOW ME THE CODE

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

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
struct Node *head = NULL;
sem_t psem;//生产者信号量
sem_t csem;//消费者信号量


void *producer(void *arg);//生产者函数
void *consumer(void *arg);//消费者函数

int main(int argc, char **argv)
{
    pthread_t ptid[5];//生产者线程
    pthread_t ctid[5];//消费者线程

    sem_init(&psem, 0, 5);//初始化生产者信号量,生产者线程拥有1个信号灯
    sem_init(&csem, 0, 0);//初始化消费者信号量,消费者线程拥有0个信号灯
    //如此这般的意义是使生产者线程先运行

    for(int i = 0; i < 5; i++){//初始化线程
        pthread_create(&ptid[i], NULL, producer, NULL);
        pthread_create(&ctid[i], NULL, consumer, NULL);
    }
    
    sem_destroy(&psem);//反初始化信号量
    sem_destroy(&csem);
    pthread_mutex_destroy(&mutex);
    
    for(int i = 0; i < 5; i++){//回收线程
        pthread_join(ptid[i], NULL);
        pthread_join(ctid[i], NULL);
    }

    return 0;
}

void *producer(void *arg)
{
    while(1){
        sem_wait(&psem);//生产者拿一个信号灯
        pthread_mutex_lock(&mutex);
        //上面两句顺序不能颠倒,要不然会产生死锁

        struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));//创建一个新节点
        pnew -> number = rand() % 100;//初始化
        pnew -> next = head;//链接节点
        head = pnew;//头结点前移
        printf("Producer: number = %d, tid = %ld\n", pnew -> number, pthread_self());
        pthread_mutex_unlock(&mutex);

        sem_post(&csem);//通知消费者消费
        sleep(rand() % 3);
    }

    return NULL;
}

void *consumer(void *arg)
{
    while(1){
        sem_wait(&csem);
        pthread_mutex_lock(&mutex);
        //以上两句顺序不可颠倒

        struct Node *pnode = head;//生产者先运行确保此处节点挪动逻辑不会出错
        printf("Consumer: number = %d, tid = %ld\n", pnode -> number, pthread_self());
        head = pnode -> next;
        free(pnode);
        pthread_mutex_unlock(&mutex);

        sem_post(&psem);//通知生产者生产

        sleep(rand() % 3);
    }

    return NULL;
}
  • 在效率提升的同时,我们不仅遇到了要保护共享资源的麻烦,还要处理可能出现死锁的情况,在上述代码中若调换某两行代码,则会产生死锁,很细节,但很重要
// 消费者
sem_wait(&csem);
pthread_mutex_lock(&mutex);

// 生产者
sem_wait(&csem);
pthread_mutex_lock(&mutex);

若将其替换为

//消费者
pthread_mutex_lock(&mutex);
sem_wait(&csem);

// 生产者
pthread_mutex_lock(&mutex);
sem_wait(&csem);
  • 若消费者某一线程先于生产者线程运行:
    • 此线程成功给互斥锁加锁,然后运行sem_wait(&csem),因为生产者尚未生产,所以此线程堵塞在sem_wait处
    • 其余消费者线程堵塞在互斥锁处
    • 对于生产者,任意生产者线程调用pthread_mutex_lock(&mutex)时,因为互斥锁已经上锁,所以所有的生产者线程全都被阻塞
    • 至此所有的线程都阻塞,死锁产生

参考:

- 线程同步|爱编程的大丙

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-14 09:47:23  更:2022-05-14 09:49:04 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/10 18:23:22-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码