使用同步信号量和互斥信号量解决生产者和消费者问题

news2024/12/24 2:34:12

生产者和消费者问题

生产者和消费者问题是一个经典的进程同步问题。在这个问题中,生产者不断地向缓冲区中写入数据,而消费者则从缓冲区中读取数据。生产者进程和消费者进程对缓冲区的操作是互斥的,即任意时刻只能有一个进程对这个缓冲区进行操作。同时,生产者进程在进入缓冲区之前,先要检查缓冲区是否已满,如果缓冲区已满,则必须等待消费者进程将数据取出才能写入数据。同样地,消费者进程在从缓冲区读取数据之前,也要判断缓冲区是否为空,如果为空,则必须等待生产者进程写入数据才能读取数据。因此,生产者进程和消费者进程之间存在进程同步现象。

什么是同步信号量和互斥信号量

在多线程编程中,同步信号量(synchronization semaphore)和互斥信号量(mutex semaphore)是两种常用的同步机制。它们都是通过操作信号量来实现线程之间的同步和互斥访问共享资源。

  1. 同步信号量(Synchronization Semaphore): 同步信号量用于实现线程之间的同步,确保线程按照特定的顺序执行。它是一种计数信号量,初始值通常为0,当线程需要等待某个条件满足时,会调用等待(wait)操作,如果条件不满足,线程将被阻塞,直到其他线程通过发信号(signal)操作将信号量的计数值增加,使得条件满足,阻塞的线程被唤醒并继续执行。同步信号量常用于解决生产者-消费者问题、线程的顺序执行等场景。
  2. 互斥信号量(Mutex Semaphore): 互斥信号量用于实现线程之间的互斥,确保同一时间只有一个线程能够访问共享资源。它是一种二进制信号量,初始值通常为1,当线程需要访问共享资源时,会调用加锁(lock)操作,如果互斥信号量的计数值为1,则线程可以继续执行临界区代码,并将互斥信号量的计数值减1,表示锁定资源。如果互斥信号量的计数值为0,表示资源已被其他线程锁定,当前线程将被阻塞,直到资源解锁,即其他线程释放锁,互斥信号量的计数值变为1。互斥信号量常用于解决竞争条件和避免多线程访问共享资源的冲突。

实现思路

在开始实验之前,我们需要分析计算机系统中对资源的分配与释放过程。在计算机系统中,每个进程都可以消费或生产某类资源。当系统中的某个进程使用某一资源时,可以视为消耗,将该进程称为消费者;而当某个进程释放资源时,则它就相当于一个生产者。

基于以上思路,我们将进行以下步骤:

  1. 定义生产者与消费者问题中的各种数据结构,并初始化信号量。
  2. 创建生产者和消费者进程,利用信号量实现生产者与消费者之间的同步与互斥。

实现步骤

1. 定义数据结构和信号量

首先,我们需要定义生产者与消费者问题中的数据结构和信号量。在本例中,我们使用数组作为缓冲区,并定义一个信号量用于控制缓冲区的访问。

#define BUFFER_SIZE 5  // 缓冲区大小
int buffer[BUFFER_SIZE];  // 缓冲区数组

HANDLE semaphore;  // 信号量句柄

2. 初始化信号量

接下来,我们需要初始化信号量。在本例中,我们将信号量的初始值设置为缓冲区的大小,表示缓冲区为空。

semaphore = CreateSemaphore(NULL, BUFFER_SIZE, BUFFER_SIZE, NULL);

3. 创建生产者和消费者进程

我们使用CreateThread()函数创建生产者和消费者进程,并指定相应的线程函数。

HANDLE producerThread = CreateThread(NULL, 0, producerFunction, NULL, 0, NULL);
HANDLE consumerThread = CreateThread(NULL, 0, consumerFunction, NULL, 0, NULL);

4. 实现生产者函数

生产者函数用于模拟生产者不断向缓冲区中写入数据的过程。在写入数据之前,生产者需要检查缓冲区是否已满,如果已满,则需要等待消费者将数据取出。

DWORD WINAPI producerFunction(LPVOID lpParam) {
  int item = 0;  // 要生产的数据项
  while (true) {
    // 检查缓冲区是否已满
    WaitForSingleObject(semaphore, INFINITE);

    // 将数据写入缓冲区
    buffer[in] = item;
    printf("Produced item: %d\n", item);
    in = (in + 1) % BUFFER_SIZE;
    item++;

    // 释放信号量,表示生产者已完成写入
    ReleaseSemaphore(semaphore, 1, NULL);
  }
  return 0;
}

5. 实现消费者函数

消费者函数用于模拟消费者从缓冲区中读取数据的过程。在读取数据之前,消费者需要检查缓冲区是否为空,如果为空,则需要等待生产者将数据写入。

DWORD WINAPI consumerFunction(LPVOID lpParam) {
  int item;
  while (true) {
    // 检查缓冲区是否为空
    WaitForSingleObject(semaphore, INFINITE);

    // 从缓冲区中读取数据
    item = buffer[out];
    printf("Consumed item: %d\n", item);
    out = (out + 1) % BUFFER_SIZE;

    // 释放信号量,表示消费者已完成读取
    ReleaseSemaphore(semaphore, 1, NULL);
  }
  return 0;
}

6. 等待线程结束

最后,我们使用WaitForSingleObject()函数等待生产者和消费者线程结束,并释放相关资源。

WaitForSingleObject(producerThread, INFINITE);
WaitForSingleObject(consumerThread, INFINITE);

CloseHandle(producerThread);
CloseHandle(consumerThread);
CloseHandle(semaphore);

这样,我们就完成了使用信号量实现生产者与消费者问题的代码实现。在该实现中,生产者和消费者通过信号量进行同步和互斥,确保了生产者不会向满的缓冲区写入数据,消费者不会从空的缓冲区读取数据。

完整代码

#include<windows.h>
#include<stdio.h>
  
const int SIZE_OF_BUFFER = 5;
int ProductID = 0;
int ConsumeID = 0;
int in = 0;
int out = 0;

int g_buffer[SIZE_OF_BUFFER];
bool g_continue = true;
HANDLE g_hMutex;
HANDLE g_hFullSemaphore; 
HANDLE g_hEmptySemaphore;

void Produce(){
	printf("生产一个产品:%d号产品\n",++ProductID);
	g_buffer[in] = ProductID;
	in = (in+1)%SIZE_OF_BUFFER;
	for (int i=0;i<SIZE_OF_BUFFER;++i)
	{
		printf("%d号缓冲区:产品%d\n",i,g_buffer[i]);
		if(i==in) printf("  <--  生产\n");
		if(i==out) printf("  <--   消费\n"); 
	}
	printf("\n");
} 

void Consume(){
	printf("消耗了一个产品:%d号产品\n",g_buffer[out]);
	g_buffer[out]=0;
	out = (out+1)%SIZE_OF_BUFFER;
	for  (int  i=0;i<SIZE_OF_BUFFER;++i)
	{
		printf("%d号缓冲区:产品%d\n",i,g_buffer[i]);
		if  (i==in) printf("  <--  生产\n");
		if  (i==out) printf("  <-- 消费\n");
		
	}
	printf("\n");
	
}

DWORD  WINAPI Producer(LPVOID lpPara)
{
	while(g_continue)
	{
		WaitForSingleObject(g_hEmptySemaphore,INFINITE);
		WaitForSingleObject(g_hMutex,INFINITE);
		Produce();
		Sleep(2000);
		ReleaseMutex(g_hMutex);
		ReleaseSemaphore(g_hFullSemaphore,1,NULL); 
	}
	return 0;
}

DWORD WINAPI Consumer(LPVOID lpPara)
{
	while(g_continue)
	{
		WaitForSingleObject(g_hFullSemaphore,INFINITE);
		WaitForSingleObject(g_hMutex,INFINITE);
		Consume();
		Sleep(2000);
		ReleaseMutex(g_hMutex);
		ReleaseSemaphore(g_hEmptySemaphore,1,NULL);
	}
	return 0;
 } 
 
 	
int main()
{
    // 创建互斥信号量 mutex
    g_hMutex = CreateMutex(NULL, FALSE, NULL);

    // 创建同步信号量 full
    g_hFullSemaphore = CreateSemaphore(NULL, 0, SIZE_OF_BUFFER, NULL);

    // 创建同步信号量 empty
    g_hEmptySemaphore = CreateSemaphore(NULL, SIZE_OF_BUFFER, SIZE_OF_BUFFER, NULL);

    // 调整生产者线程和消费者线程的个数,看看结果有何不同。
    const int PRODUCERS_COUNT = 6;    // 生产者的个数
    const int CONSUMERS_COUNT = 2;    // 消费者的个数

    const int THREADS_COUNT = PRODUCERS_COUNT + CONSUMERS_COUNT; // 计算总的线程数
    HANDLE hThreads[THREADS_COUNT]; // 各线程的 handle
    DWORD producerID[PRODUCERS_COUNT]; // 生产者线程的标识符
    DWORD consumerID[CONSUMERS_COUNT]; // 消费者线程的标识符

    // 创建生产者线程
    for (int i = 0; i < PRODUCERS_COUNT; ++i)
    {
        hThreads[i] = CreateThread(NULL, 0, Producer, NULL, 0, &producerID[i]);
        if (hThreads[i] == NULL)
        {
            return -1;
        }
    }

    // 创建消费者线程
    for (int j = 0; j < CONSUMERS_COUNT; ++j)
    {
        hThreads[PRODUCERS_COUNT + j] = CreateThread(NULL, 0, Consumer, NULL, 0, &consumerID[j]);
        if (hThreads[j] == NULL)
        {
            return -1;
        }
    }

    // 控制线程终止
    while (g_continue)
    {
        if (getchar() == '\n') // 按回车后终止程序运行
        {
            g_continue = false;
        }
    }

    return 0;
}

运行截图:

image.png

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

RTU电流采集上传

RTU电流采集上传 案例说明器件 物联网平台开发代码修改三元组 测试 案例说明 本案例使用HD1&#xff08;RTU&#xff09;检测外部电流&#xff0c;并将电流上传阿里云端。 压力传感器输出电流信号&#xff0c;读取压力传感器数值时需要检测电流大小。haasHD1(RTU)有两路ADC—…

深度学习应用篇-元学习[16]:基于模型的元学习-Learning to Learn优化策略、Meta-Learner LSTM

【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化算法、卷积模型、序列模型、预训练模型、对抗神经网络等 专栏详细介绍&#xff1a;【深度学习入门到进阶】必看系列&#xff0c;含激活函数、优化策略、损失函数、模型调优、归一化…

[C语言实现]数据结构堆之《害怕二叉树所以天赋全点到堆上了》

&#x1f970;作者: FlashRider &#x1f30f;专栏: 数据结构 &#x1f356;知识概要&#xff1a;详解堆的概念、小根堆与大根堆的区别、以及代码实现。 目录 什么是堆&#xff1f; 如何实现堆&#xff1f; 代码实现堆(小根堆) 定义堆以及堆的初始化和销毁。 堆的插入 堆…

LeetCode·每日一题·1177. 构建回文串检测·前缀和

作者&#xff1a;小迅 链接&#xff1a;https://leetcode.cn/problems/can-make-palindrome-from-substring/solutions/2309940/qian-zhui-he-zhu-shi-chao-ji-xiang-xi-by-n3ps/ 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 著作权归作者所有。商业转载请联系作者获…

最新水文水动力模型在城市内涝、城市排水、海绵城市规划设计中深度应用

随着计算机的广泛应用和各类模型软件的发展&#xff0c;将排水系统模型作为城市洪灾评价与防治的技术手段已经成为防洪防灾的重要技术途径。本次培训将聚焦于综合利用GIS及CAD等工具高效地进行大规模城市排水系统水力模型的建立&#xff0c;利用SWMM实现排水系统水力模拟。讲解…

【RH850/U2A】:GreenHills编译配置

GreenHills编译配置 GreenHills语法.gpj文件.opt文件示例GreenHills编译器在编译我们的文件时涉及它需要哪些文件及相关配置呢?带着疑问我们开始来梳理。 我们还是以具体示例来展开(硬件平台:RH850 U2A8) GreenHills语法 一般我们是需要查看它的帮助文档的,文档在哪里呢?…

CSP第二轮/NOIP 比赛注意事项

一、在哪里写代码 主办方会提前在桌面已在 E 盘根目录下建立以考生准考证编号命名的文件夹,考生应检查该文件夹名称是否正确(包括编号及大小写字母),如有错误须立即上报监考人员,由监考人员进行更改。确认无误后,考生须为每道试题再单独建立一个子文件夹,子文件夹名与对应…

某互联网银行绿色金融背后的“安全秘诀”

​随着银保监会出台《银行业保险业绿色金融指引》、人民银行牵头制定《G20转型金融框架》的发布&#xff0c;金融行业正在持续加大对绿色金融支持力度。某互联网银行为了响应号召&#xff0c;采用数字化无纸化办公&#xff0c;线上零接触服务减少大量碳排放&#xff0c;成为国内…

oracle rac架构解读

一、oracle 数据库架构 单节点数据库&#xff0c;如果实例宕机了&#xff0c;如果一个业务链接在实例上面&#xff0c;那么这个业务就中断了。这个时候系统就不具有可用性了&#xff0c;那么这个时候单节点的可用性是很差的。 对于RAC来说&#xff0c;和单实例一样&#xff0c;…

新榜 | 小红书美妆用户趋势洞察报告

目前&#xff0c;小红书上聚集了大量年轻、高知的女性美妆用户&#xff0c;她们倾向于在小红书平台分享美妆护肤产品和使用经验&#xff0c;用户间的互动、分享氛围浓厚&#xff1b;而这些评论互动也传递了用户的真实诉求&#xff0c;理解用户的关注点对于企业和品牌来说将具有…

Vue中如何进行数据导入与Excel导入

Vue中如何进行数据导入与Excel导入 Vue是一款非常流行的JavaScript框架&#xff0c;它提供了一套用于构建用户界面的工具和库。在Vue中&#xff0c;我们可以使用多种方式来导入数据&#xff0c;包括从服务器获取数据、从本地存储获取数据、从文件中读取数据等等。其中&#xf…

如何提高职场沟通能力

如何提高职场沟通能力 在现代职场中&#xff0c;良好的沟通能力不仅有助于我们更好地完成工作任务&#xff0c;还能提高团队协作效率&#xff0c;降低矛盾和误解。本文将为你提供一些建议和技巧&#xff0c;帮助你提高职场沟通能力。 1. 倾听 倾听是沟通中最重要的技能之一…

社招准备和面试题

这次就整理下这次社招都做了哪些准备以及自己面试过的题目。 首先就是专业知识的准备&#xff0c;看了很多常用的机器学习算法&#xff0c;并对其算法做了推导。看了深度学习推荐系统这本书里面的模型&#xff0c;对自己简历中涉及到的模型重点掌握&#xff0c;比如DIN、DIEN、…

创客匠人6月功能更新:服务商管理、直播、学员版APP全新上线

创客匠人6月功能更新&#xff0c;包括服务商管理、直播、学员版APP、圈子、商城、店铺等众多产品升级&#xff0c;我们一起来看看吧。 正式升级时间&#xff1a;6月20日 一、服务商管理 1.服务商模块排版优化&#xff1a;支持查看整个团队的用户信息和业绩明细。 2.支持记…

嵌套滚动实践:onInterceptTouchEvent与NestedScrolling【实用为准】

嵌套滚动&#xff1a;内外两层均可滚动&#xff0c;比如上半部分是一个有限的列表&#xff0c;下半部分是WebView&#xff0c;在内层上半部分展示到底的时候&#xff0c;外部父布局整体滚动内部View&#xff0c;将底部WevView拉起来&#xff0c;滚动到顶部之后再将滚动交给内部…

SQL Server 无备份情况下误操作数据恢复(3)

原文链接&#xff1a;https://blog.csdn.net/dba_huangzj/article/details/8491327 问题&#xff1a; 经常看到有人误删数据&#xff0c;或者误操作&#xff0c;特别是update和delete的时候没有加where&#xff0c;然后就喊爹喊娘了。人非圣贤孰能无过&#xff0c;做错可以理解…

Verilog 高级知识点---状态机

目录 状态机 1、Mealy 状态机 2、Moore 状态机 3、三段式状态机 状态机 Verilog 是硬件描述语言&#xff0c;硬件电路是并行执行的&#xff0c;当需要按照流程或者步骤来完成某个功能时&#xff0c;代码中通常会使用很多个 if 嵌套语句来实现&#xff0c;这样就增加了代码…

2DUI跟踪3D模型,更精准的嵌套与跟踪

实现的效果&#xff1a; 1、2DUI跟踪模型指定位置&#xff0c;跟随模型移动 2、2DUI时刻面向摄像机 首先准备一个模型。如下图&#xff1a; 在此模型层级下新建Canvas&#xff08;画布&#xff09; 改显示模式为世界空间 在canvas下创建Image&#xff08;图像&#xff09; 放…

包看包会Stable Diffusion原理,新手也能看明白

知道看文章的人怎么看&#xff0c;听我讲的人经常反应的就是听不明白。于是我又在网上找了一下&#xff0c;发现这篇文章讲的很好&#xff0c;算得上是深入浅出&#xff0c;可惜是英文的&#xff0c;就把它翻译了一下&#xff1a; https://stable-diffusion-art.com/how-stabl…

一次过!快速申领软件著作权

文章目录 一次过&#xff01;快速申领软件著作权1 软件著作权的定义2 申请流程2.1 准备申请材料2.2 登录软著局申请系统并进行填写2.3 审核2.4 补正和修改申请材料2.5 接受核准并领证 3 申请材料4 注意事项5 总结 一次过&#xff01;快速申领软件著作权 申领软件著作权是保护软…