Linux多线程编程-生产者与消费者模型详解与实现(C语言)

1.什么是生成者与消费者模型

生产者-消费者模型是并发编程中的经典问题,描述了多个线程(或进程)如何安全、有效地共享有限的缓冲区资源。在这个模型中,有两种角色:

  1. 生产者(Producer):负责生成数据或者将数据放置到共享缓冲区中。

  2. 消费者(Consumer):负责从共享缓冲区中取出数据并进行处理或消费。

核心概念:

生产者和消费者模型主要涉及以下几个关键概念:

  • 共享缓冲区(Shared Buffer):生产者和消费者之间共享的有限大小的缓冲区。这个缓冲区可以是一个队列或者一个固定大小的数组。

  • 同步(Synchronization):确保生产者和消费者之间的正确协作,避免数据竞争和资源争用。例如,当缓冲区已满时,生产者应该等待;当缓冲区为空时,消费者应该等待。

  • 互斥(Mutual Exclusion):确保同一时刻只有一个线程(生产者或消费者)可以访问或操作共享缓冲区,以避免数据不一致或丢失。

具体案例:

假设有一个生产者和多个消费者的情形,以一个有界缓冲区(Bounded Buffer)为例:

  1. 共享缓冲区:假设有一个大小为10的数组作为共享缓冲区,用于存放生产者生产的产品。

  2. 生产者:负责生成产品,并将产品放入共享缓冲区中。如果缓冲区已满,生产者需要等待,直到有空间可以放置产品。

  3. 消费者:负责从共享缓冲区中取出产品并进行消费。如果缓冲区为空,消费者需要等待,直到有产品可以消费。

解决方案:

为了解决生产者-消费者模型中的同步和互斥问题,可以采用以下方法:

  • 互斥锁(Mutex):确保在同一时刻只有一个线程可以访问或修改共享缓冲区。例如,生产者和消费者在访问缓冲区前,首先要获取互斥锁,操作完成后释放锁。

  • 条件变量(Condition Variables):用于线程间的通信,如通知生产者缓冲区有空间可以放置新产品,或通知消费者缓冲区中有产品可以消费。

  • 信号量(Semaphores):用于控制对共享资源的访问,如控制缓冲区的空闲空间数量或产品数量。

示例代码:

使用C语言来实现,包括两个生产者线程、三个消费者线程,一个大小为10的共享缓冲区(使用链表实现),每个线程生成或消费一个数据后休眠1-2秒,并打印过程。我们将使用条件变量来实现线程的等待和唤醒机制。

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

#define BUFFER_SIZE 10

// 链表节点
typedef struct Node {
    int data;
    struct Node *next;
} Node;

// 全局变量
Node *head = NULL;                  // 指向链表头部的指针
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 互斥锁
pthread_cond_t empty = PTHREAD_COND_INITIALIZER;    // 缓冲区为空的条件变量
pthread_cond_t full = PTHREAD_COND_INITIALIZER;     // 缓冲区已满的条件变量
int count = 0;                      // 缓冲区当前数据项的数量

// 生产者线程函数
void *producer_func(void *arg) {
    int id = *((int *)arg);  // 生产者线程的编号
    int data = 0;
    
    while (1) {
        // 生成数据项
        data++;
        
        // 获取互斥锁
        pthread_mutex_lock(&mutex);
        
        // 等待直到缓冲区有空间
        while (count == BUFFER_SIZE) {
            printf("Producer %d: Buffer is full. Waiting...\n", id);
            //条件变量 (empty):在缓冲区已满时,生产者线程会等待在 empty 条件变量上,直到有消费者取走数据并通知它。
            pthread_cond_wait(&empty, &mutex);
        }
        
        // 将数据项放入缓冲区,头插法,取节点的时候不用遍历链表
        Node *new_node = (Node *)malloc(sizeof(Node));
        new_node->data = data;
        new_node->next = head;
        head = new_node;
        count++;
        
        printf("Producer %d: Produced data: %d\n", id, data);
        
        // 在生产者放入数据后,会通过 full 条件变量唤醒等待的消费者线程,告知它们可以消费数据了。
        pthread_cond_signal(&full);
        
        // 释放互斥锁
        pthread_mutex_unlock(&mutex);
        
        // 休眠1-2秒
        sleep(rand() % 2);
    }
    
    pthread_exit(NULL);
}

// 消费者线程函数
void *consumer_func(void *arg) {
    int id = *((int *)arg);  // 消费者线程的编号
    
    while (1) {
        // 获取互斥锁
        pthread_mutex_lock(&mutex);
        
        // 等待直到缓冲区有数据
        while (head == NULL) {
            printf("Consumer %d: Buffer is empty. Waiting...\n", id);
            //条件变量 (full):在缓冲区为空时,消费者线程会等待在 full 条件变量上,直到有生产者放入数据并通知它。
            pthread_cond_wait(&full, &mutex);
        }
        
        // 从缓冲区取出数据项
        Node *temp = head;
        head = head->next;
        int data = temp->data;
        free(temp);
        count--;
        
        printf("Consumer %d: Consumed data: %d\n", id, data);
        
        // 在消费者取走数据后,会通过 empty 条件变量唤醒等待的生产者线程,告知它们可以继续生产数据。
        pthread_cond_signal(&empty);
        
        // 释放互斥锁
        pthread_mutex_unlock(&mutex);
        
        // 休眠1-3秒
        sleep(rand() % 3 + 1);
    }
    
    pthread_exit(NULL);
}

int main() {
    pthread_t producers[2], consumers[3];
    int producer_ids[2] = {1, 2};  // 两个生产者线程的编号
    int consumer_ids[3] = {1, 2, 3};  // 三个消费者线程的编号
    
    // 初始化互斥锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&empty, NULL);
    pthread_cond_init(&full, NULL);
    
    // 创建生产者线程
    for (int i = 0; i < 2; ++i) {
        pthread_create(&producers[i], NULL, producer_func, (void *)&producer_ids[i]);
    }
    
    // 创建消费者线程
    for (int i = 0; i < 3; ++i) {
        pthread_create(&consumers[i], NULL, consumer_func, (void *)&consumer_ids[i]);
    }
    
    // 主线程等待所有子线程结束
    for (int i = 0; i < 2; ++i) {
        pthread_join(producers[i], NULL);
    }
    for (int i = 0; i < 3; ++i) {
        pthread_join(consumers[i], NULL);
    }
    
    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&empty);
    pthread_cond_destroy(&full);
    
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/799090.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

牛客周赛 Round 51 解题报告 | 珂学家

前言 题解 典题场&#xff0c; EF都有很多种解法 A. 小红的同余 性质: 相邻两数互质 x ( m 1 ) / 2 x (m1)/2 x(m1)/2 m int(input())print ((m 1) // 2)B. 小红的三倍数 性质: 各个位数之和是3的倍数&#xff0c;可被3整除 和数的组合顺序无关 n int(input()) arr…

Mysql数据表的约束(下)

3.默认值约束(default) 与非空约束的命令一致,因为都属于列级约束,因此只需将not null改为default 默认值即可 删除默认值约束: 4.主键约束(primary key) 表示给一张表格设置了一个唯一标识,为了更快的去通过唯一的数据去准确的查找到每一条记录,一半咱们在创建表…

Netgear WN604 downloadFile.php 信息泄露漏洞复现(CVE-2024-6646)

0x01 产品简介 NETGEAR WN604是一款由NETGEAR(网件)公司生产的无线接入器(或无线路由器)提供Wi-Fi保护协议(WPA2-PSK, WPA-PSK),以及有线等效加密(WEP)64位、128位和152位支持,保障网络安全。同时支持MAC地址认证、802.1x RADIUS以及EAP TLS、TTLS、PEAP等安全机制,…

昇思25天学习打卡营第15天|基于MobileNetv2的垃圾分类

一、关于MobileNetv2 MobileNet网络专注于移动端、嵌入式或IoT设备的轻量级CNN网络。MobileNet网络使用深度可分离卷积&#xff08;Depthwise Separable Convolution&#xff09;的思想在准确率小幅度降低的前提下&#xff0c;大大减小了模型参数与运算量。并引入宽度系数 α和…

Paddle 打包部署

PaddleOCR 打包部署exe 心酸历程 PaddleOCR部署exe模式PaddleOCR安装到本地(稍后有时间再写)PaddleOCR打包过程异常问题记录&#xff01;&#xff01;&#xff01;&#xff01;No such file or directory: D:\\py_project\\paddleOCR\\dist\\paddleOCR\\_internal\\paddleocr\\…

如何指定多块GPU卡进行训练-数据并行

训练代码&#xff1a; train.py import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, Dataset import torch.nn.functional as F# 假设我们有一个简单的文本数据集 class TextDataset(Dataset):def __init__(self, te…

Linux系统密码重置

实验环境&#xff1a; Centos 7.9 背景&#xff1a; 找回root用户密码 1、首先&#xff0c;启动Linux系统进入开机界面&#xff0c;在界面中快速点击‘e’进入编辑界面&#xff0c;如图&#xff1a; 2、进入编辑界面会后往下翻找到“Linux16”内容所在的行数&#xff0c;在&q…

景联文科技打造千万级高质量中文数字专利数据库,赋能知识产权领域AI创新

专利大模型是在专利数据分析、检索、理解和生成等领域运用的大规模机器学习模型。 已被应用在多个场景中&#xff0c;包括但不限于专利检索优化、专利文本的自动化撰写、专利价值评估、技术趋势预测、专利侵权检测、以及专利组合管理和战略规划等。 专利大模型依赖于海量的数字…

python--实验 11 模块

目录 知识点 模块基础 模块使用方式 自定义模块示例 模块的有条件执行 Python包结构 定义和导入包 常用第三方库及安装 实例代码 第三方库自动安装脚本 Python标准库介绍 PyInstaller 小结 实验 1.(基础题)制作文本进度条。 2.(基础题) 蒙特卡罗方法计算圆周率…

数据结构(单链表(1))

前言 线性表中有着许多的结构&#xff0c;如顺序表和链表。而单链表则是链表的最基础的一种形式&#xff0c;下面就让我们对其做一个了解。 概念 概念&#xff1a;链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次…

【python】OpenCV—European Article Number

参考学习来自&#xff1a;OpenCV基础&#xff08;25&#xff09;条码和二维码扫的生成与识别 1 条形码介绍 EAN-13是欧洲物品编码&#xff08;European Article Number&#xff09;的缩写&#xff0c;是一种广泛使用的条形码标准&#xff0c;特别是在超级市场和其它零售业中。…

1.33、激活可视化卷积神经网络(matalb)

1、激活可视化卷积神经网络原理及流程 激活可视化&#xff08;Activation Visualization&#xff09;指的是通过可视化神经网络中激活函数的输出&#xff0c;来理解神经网络是如何学习并提取特征的过程。在卷积神经网络&#xff08;CNN&#xff09;中&#xff0c;我们可以通过…

独立开发者系列(25)——大白话进程

很多小型的规模场景限制下&#xff0c;复杂概念弊端大于利端。不同模式的实现&#xff0c;是根据具体需求来判定&#xff0c;但是理解底层最基础的原理有助于理解很多工具背后的诞生。比如php的swoole workerman 要解决的问题。 首先理解&#xff0c;进程概念&#xff0c;进程…

【竞技宝】欧洲杯:南门的保守害了英格兰

2024欧洲杯已经结束&#xff0c;决赛中西班牙2比1击败英格兰&#xff0c;队史第四次拿到欧洲杯冠军&#xff0c;而英格兰连续两届比赛进入决赛却功亏一篑让人唏嘘不已。赛后关于英格兰主帅索斯盖特是否留任的问题再次被炒上热搜&#xff0c;不少球迷和媒体认为索帅拥有豪华阵容…

华为大咖说 | 企业应用AI大模型的“道、法、术” —— 法:落地篇 (下)

本文作者&#xff1a;郑岩&#xff08;华为云AI变革首席专家&#xff09;全文约3554字&#xff0c;阅读约需9分钟 上周&#xff0c;我们探讨了企业应用AI大模型的“道、法、术”——法&#xff1a;落地篇“AI变革五阶八步法”的前四步内容&#xff08;华为大咖说 | 企业应用AI大…

使用 HttpServlet 接收网页的 post/get 请求

前期工作&#xff1a;部署好 idea 和 一个 web 项目 idea(2021),tomcat(9) ->创建一个空的项目 -> 新建一个空的模块 -> 右键单击模块 选择 Add..Fra.. Sup.. -> 勾选Web App...后点击OK -> 点击 file - Project Struc... -> 选择刚刚的模块 -> 点…

C++初阶:类和对象(中)

一.类的默认成员函数 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。⼀个类&#xff0c;我们不写的情况下编译器会默认生成以下6个默认成员函数。默认成员函数很重要&#xff0c;也比较复杂&#xff1a; 二.构造函数 &#xff08;…

GPT-4从0到1搭建一个Agent简介

GPT-4从0到1搭建一个Agent简介 1. 引言 在人工智能领域&#xff0c;Agent是一种能够感知环境并采取行动以实现特定目标的系统。本文将简单介绍如何基于GPT-4搭建一个Agent。 2. Agent的基本原理 Agent的核心是感知-行动循环&#xff08;Perception-Action Loop&#xff09;…

电脑文件误删除如何恢复?Top12电脑数据恢复软件汇总合集!(图文详解)

电脑文件误删除如何恢复&#xff1f;在日常使用电脑过程中&#xff0c;我们经常会遇到意外删除文件的情况。可能是因为按错了按键、误操作了鼠标&#xff0c;或者意外格式化了存储设备。这些情况都可能导致重要的文件不小心被删除。但是不用担心&#xff0c;有许多专业的数据恢…

从 Pandas 到 Polars 十八:数据科学 2025,对未来几年内数据科学领域发展的预测或展望

我在2021年底开始使用Polars和DuckDB。我立刻意识到这些库很快就会成为数据科学生态系统的核心。自那时起&#xff0c;这些库的受欢迎程度呈指数级增长。 在这篇文章中&#xff0c;我做出了一些关于未来几年数据科学领域的发展方向和原因的预测。 这篇文章旨在检验我的预测能力…