C# 实现跨进程条件变量

C# 进程通信系列

第一章 共享内存
第二章 条件变量(本章)
第三章 消息队列


文章目录

  • C# 进程通信系列
  • 前言
  • 一、关键实现
    • 1、用到的主要对象
    • 2、初始化区分创建和打开
    • 3、变量放到共享内存
    • 4、等待和释放逻辑
  • 二、完整代码
  • 三、使用示例
    • 1、同步控制
    • 2、跨进程控制
  • 总结


前言

C#提供的多进程同步对象有互斥锁和信号量,但是并没有条件变量。虽然信号量条件变量一定程度可以等效,但是具体的使用还是会有区别。比如说消息队列用条件变量就比信号量方便,用信号量要么缺乏灵活性,要么辅助代码已经和实现一个条件变量没区别了。本文提供了一种条件变量的实现方法,可以用于进程间的同步控制。


一、关键实现

1、用到的主要对象

下列对象都是跨进程的

//互斥变量,
Mutex _mtx;
//等待发送信号量
Semaphore _waitSem;
//等待完成信号量
Semaphore _waitDone;
//共享内存,用于存储计数变量
MemoryMappedFile _mmf;
//共享内存的读写对象
MemoryMappedViewAccessor _mmva;

2、初始化区分创建和打开

利用Mutex判断是创建还是打开

bool isCreateNew;
_mtx = new Mutex(false, name, out isCreateNew);if(isCreateNew){//只能在创建时,初始化共享变量}

3、变量放到共享内存

条件变量需要的计算对象就两个Waiting、Signals表示等待数和释放数。

//放到共享内存的数据
struct SharedData
{public int Waiting;public int Signals;
}
SharedData Data
{set{_mmva.Write(0, ref value);}get{SharedData ret;_mmva.Read(0, out ret);return ret;}
}

4、等待和释放逻辑

参考了SDL2的条件变量实现,具体略。有兴趣的朋友可以自行查找源码查看。


二、完整代码

using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;namespace AC
{/************************************************************************* @Project:  	AC::ConditionVariable* @Decription:   条件变量*                支持跨进程* @Verision:  	v1.0.0    *              更新日志*              v1.0.0:实现基本功能              * @Author:  	Xin* @Create:  	2024/07/18 15:25:00* @LastUpdate:  2024/07/21 20:53:00************************************************************************* Copyright @ 2025. All rights reserved.************************************************************************/class ConditionVariable : IDisposable{/// <summary>/// 构造方法/// </summary>public ConditionVariable(){bool isCreateNew;Initialize(null, out isCreateNew);}/// <summary>/// 构造方法/// </summary>/// <param name="name">唯一名称,系统级别,不同进程创建相同名称的本对象,就是同一个条件变量。</param>public ConditionVariable(string? name){bool isCreateNew;Initialize(name, out isCreateNew);}/// <summary>/// 构造方法/// </summary>/// <param name="name">唯一名称,系统级别,不同进程创建相同名称的本对象,就是同一个条件变量。</param>/// <param name="isCreateNew">表示是否新创建,是则是创建,否则是打开已存在的。</param>public ConditionVariable(string? name, out bool isCreateNew){Initialize(name, out isCreateNew);}/// <summary>/// 等待/// </summary>/// <param name="outerMtx">外部锁</param>public void WaitOne(Mutex outerMtx){WaitOne(Timeout.InfiniteTimeSpan, outerMtx);}/// <summary>/// 等待超时/// </summary>/// <param name="timeout">超时时间</param>/// <param name="outerMtx">外部锁</param>/// <returns>是则成功,否则超时</returns>public bool WaitOne(TimeSpan timeout, Mutex outerMtx){bool isNotTimeout;//记录等待数量_mtx.WaitOne();var ws = Data;ws.Waiting++;Data = ws;_mtx.ReleaseMutex();//解除外部的互斥锁,让其他线程可以进入条件等待。outerMtx.ReleaseMutex();//等待信号isNotTimeout = _waitSem.WaitOne(timeout);_mtx.WaitOne();ws = Data;if (isNotTimeout && ws.Signals > 0){//通知发送信号的线程,等待完成。_waitDone.Release();ws.Signals--;}ws.Waiting--;Data = ws;_mtx.ReleaseMutex();//加上外部互斥锁,还原外部的锁状态。outerMtx.WaitOne();return !isNotTimeout;}/// <summary>/// 释放,通知/// </summary>public void Release(){_mtx.WaitOne();var ws = Data;if (ws.Waiting > ws.Signals){ws.Signals++;Data = ws;_waitSem.Release();_mtx.ReleaseMutex();_waitDone.WaitOne();}else{_mtx.ReleaseMutex();}}/// <summary>/// 释放全部,广播/// </summary>public void ReleaseAll(){_mtx.WaitOne();var ws = Data;if (ws.Waiting > ws.Signals){int waiting = ws.Waiting - ws.Signals;ws.Signals = ws.Waiting;Data = ws;_waitSem.Release(waiting);_mtx.ReleaseMutex();_waitDone.WaitOne(waiting);}else{_mtx.ReleaseMutex();}}/// <summary>/// 销毁对象,只会销毁当前实例,如果多个打开同个名称,其他对象不受影响/// </summary>public void Dispose(){_mtx.Dispose();_waitSem.Dispose();_waitDone.Dispose();_mmva.Dispose();_mmf.Dispose();}void Initialize(string? name, out bool isCreateNew){Mutex? mtx = null;Semaphore? waitSem = null;Semaphore? waitDone = null;MemoryMappedFile? mmf = null;MemoryMappedViewAccessor? mmva = null;try{mtx = _mtx = new Mutex(false, name, out isCreateNew);_mtx.WaitOne();try{waitSem = _waitSem = new Semaphore(0, int.MaxValue, name + ".cv.ws");waitDone = _waitDone = new Semaphore(0, int.MaxValue, name + ".cv.wd");var _shmPath = Path.Combine(_TempDirectory, name + ".cv");mmf = _mmf = MemoryMappedFile.CreateFromFile(File.Open(_shmPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite), null, Marshal.SizeOf<SharedData>(), MemoryMappedFileAccess.ReadWrite, HandleInheritability.Inheritable, false);mmva = _mmva = _mmf.CreateViewAccessor();if (isCreateNew) Data = new SharedData() { Signals = 0, Waiting = 0 };}finally{_mtx.ReleaseMutex();}}catch{mtx?.Dispose();waitSem?.Dispose();waitDone?.Dispose();mmf?.Dispose();mmva?.Dispose();isCreateNew = false;throw;}}Mutex _mtx;Semaphore _waitSem;Semaphore _waitDone;MemoryMappedFile _mmf;MemoryMappedViewAccessor _mmva;struct SharedData{public int Waiting;public int Signals;}SharedData Data{set{_mmva.Write(0, ref value);}get{SharedData ret;_mmva.Read(0, out ret);return ret;}}static string _TempDirectory = Path.GetTempPath() + "EE3E9111-8F65-4D68-AB2B-A018DD9ECF3C";}
}

三、使用示例

1、同步控制

using AC;
ConditionVariable cv = new ConditionVariable();
Mutex mutex = new Mutex();
string text = "";
//子线程发送消息
new Thread(() =>
{int n = 0;while (true){mutex.WaitOne();text = (n++).ToString();//通知主线程cv.Release();mutex.ReleaseMutex();}}).Start();
//主线程接收消息
while (true)
{mutex.WaitOne();//等待子消息cv.WaitOne(mutex);Console.WriteLine(text);mutex.ReleaseMutex();
}

在这里插入图片描述

2、跨进程控制

进程A

//不同进程名称相同就是同一个对象
ConditionVariable cv = new ConditionVariable("cv1");
Mutex mutex = new Mutex(false,"mx1");
//进程A发送消息
while (true)
{mutex.WaitOne();//共享进程读写略//通知进程Bcv.Release();mutex.ReleaseMutex();
}

进程B

//不同进程名称相同就是同一个对象
ConditionVariable cv = new ConditionVariable("cv1");
Mutex mutex = new Mutex(false,"mx1");
//进程B接收消息
while (true)
{mutex.WaitOne();//等待进A程消息cv.WaitOne(mutex);//共享进程读写略Console.WriteLine("收到进程A消息");mutex.ReleaseMutex();
}

在这里插入图片描述


总结

以上就是今天要讲的内容,之所以实现这样一个对象是因为,笔者在写跨进程队列通信,用信号量实现发现有所局限,想要完善与重写一个条件变量差异不大,索性直接实现一个条件变量,提供给队列使用,同时还具体通用性,在其他地方也能使用。总的来说,条件变量还是有用的,虽然需要遇到相应的使用场景才能意识到它的作用。

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

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

相关文章

202496读书笔记|《飞花令·菊(中国文化·古典诗词品鉴)》——荷尽已无擎雨盖,菊残犹有傲霜枝

202496读书笔记|《飞花令菊&#xff08;中国文化古典诗词品鉴&#xff09;》——荷尽已无擎雨盖&#xff0c;菊残犹有傲霜枝 《飞花令菊&#xff08;中国文化古典诗词品鉴&#xff09;》素心落雪 编著。飞花令得名于唐代诗人韩翃《寒食》中的名句“春城无处不飞花”&#xff0c…

KubeSphere核心实战_kubesphere全功能安装_启用kubesphere的热插拔插件---分布式云原生部署架构搭建037

然后我们开始安装kubesphere,首先进入官网点击kubernetes安装 可以看到对应的,条件说kubernetes要在1.20.x以上,我们的是 1.20.9,然后cpu硬件满足,然后,默认存储类型,上一节我们安装好了 然后就可以开始,去下载两个配置文件可以看到上面的两个配置文件 这两个文件,上面是直接…

跨域问题:预检请求

解决跨域问题之预检请求 预检请求&#xff08;Preflight Request&#xff09;是跨域资源共享&#xff08;CORS&#xff09;中用于安全检查的一种机制。 它是由浏览器自动发起的一个OPTIONS请求&#xff0c;目的是在实际跨域请求之前&#xff0c;询问服务器是否允许这次跨域操…

学习TS -类型

let a:number10;//数值型 let b:string"zhaoya";//字符串型 let c:booleantrue;//布尔类型 let d:10;//字面量 let e:any10;//任意类型&#xff0c;可以给别人赋值&#xff0c;而且不会提示报错 let f:unknown50;//未知类型&#xff0c;他给别人赋值&#xff0c;赋值…

kafka开启kerberos和ACL

作者&#xff1a;恩慈 一、部署kafka-KB包 1&#xff0e;上传软件包 依次点击 部署中心----部署组件----上传软件包 选择需要升级的kafka版本并点击确定 2&#xff0e;部署kafka 依次点击部署中心----部署组件----物理/虚拟机部署----选择集群----下一步 选择手动部署-…

三角函数cos

# 三角函数cos教程 ## 1. 三角函数简介 三角函数是数学中研究角度与长度关系的一组函数&#xff0c;其中最基本的三个函数是正弦&#xff08;sin&#xff09;、余弦&#xff08;cos&#xff09;和正切&#xff08;tan&#xff09;。这些函数在几何学、物理学、工程学等领域有…

消费金融系统开发回忆录

架构设计图 整个支付链路上的功能 支付系统应该有&#xff1a;账户管理、渠道管理、支付管理、对账管理、清算管理、结算管理 一笔支付订单&#xff0c;在支付系统侧就是要记录清楚&#xff0c;谁发起的、对哪个商品进行支付、通过哪个渠道支付、支付时间、支付结果等…

C++基础入门(二)(函数重载,引用,内联函数,nullptr)

目录 一. 函数重载 1. 概念 2. 实现 (1). 参数类型不同 (2). 参数个数不同 (3). 参数类型顺序不同 3. 注意事项 (1). 返回值不能作为重载的条件 (2). 不能仅按函数返回类型重载 (3). 与缺省参数的问题 二. 引用 1. 概念和定义 2. 引用的特性 (1). 引用在定义时必须…

优化Python爬虫:多线程助力数据采集高速通道

多线程爬虫&#xff1a;并行艺术的实践 一、多线程基础 多线程&#xff0c;顾名思义&#xff0c;即一个程序内部同时运行多个执行流&#xff0c;它们共享同一块内存空间。对于I/O密集型任务&#xff0c;如爬虫频繁的网络请求和文件读写&#xff0c;多线程能够显著提高任务的吞…

LDR6020双盲插便携显示器应用

随着USB Type-C接口的普及&#xff0c;越来越多的手机和笔记本电脑都支持通过C接口输出视频。这个小巧而精密的接口&#xff0c;大有把传统的HDMI和DisplayPort接口取而代之的架势。特别是usb4的推出&#xff0c;更是为USB TYPE-C接口一统有线接口形态奠定了基础。 单USB-C接口…

python入门课程Pro(1)--数据结构及判断

数据结构及判断 第1课 复杂的多向选择1.if-elif-else2.if嵌套3.练习题&#xff08;1&#xff09;大招来了&#xff08;2&#xff09;奇数还是偶数&#xff08;3&#xff09;简洁代码 第2课 数据与判断小结1.变量2.格式化输出3.逻辑运算-或与非4.判断条件5.练习题&#xff08;1&…

查找算法②-二分查找/折半查找

一、算法原理 二分查找算法又称折半查找算法&#xff0c;每次将待查找的序列一分为二&#xff08;经实验&#xff0c;一分为二总是比一分为四、一分为八等等快&#xff09;&#xff0c;首先用待查找的目标值target与中间值middle比较&#xff0c;如果待查找值在左侧则直接舍弃右…

【手撕数据结构】拿捏双向链表

目录 链表介绍初始化链表销毁链表查找节点打印链表增加节点尾插头插在指定位置之后插入节点 删除节点尾删头删删除指定位置节点 链表判空 链表介绍 前面说到&#xff0c;链表的结构一共有八种&#xff1a;带头单向循环链表、带头单向非循环链表、带头双向循环链表、带头双向非…

【初阶数据结构】5.栈和队列

文章目录 1.栈1.1 概念与结构1.2 栈的实现2.队列2.1 概念与结构2.2 队列的实现3.栈和队列算法题3.1 有效的括号3.2 用队列实现栈3.3 用栈实现队列3.4 设计循环队列 1.栈 1.1 概念与结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操…

C语言宏定义格式化控制台打印

写了个简单的控制台打印代码&#xff0c;有三种打印级别 DEBUG INFO ERROR&#xff0c;支持颜色打印&#xff0c;支持时间打印 在MSVC环境中使用 #include <time.h> #include <string.h> #include <stdio.h>/* log level */ #define LOG_LEVEL_DEBUG (1) #d…

【STM32 HAL库】全双工I2S+双缓冲DMA的使用

1、配置I2S 我们的有效数据是32位的&#xff0c;使用飞利浦格式。 2、配置DMA **这里需要注意&#xff1a;**i2s的DR寄存器是16位的&#xff0c;如果需要发送32位的数据&#xff0c;是需要写两次DR寄存器的&#xff0c;所以DMA的外设数据宽度设置16位&#xff0c;而不是32位。…

Log4j2原理及应用详解(十三)

本系列文章简介&#xff1a; 在软件开发过程中&#xff0c;日志记录是一个不可或缺的重要环节。它不仅帮助开发者在开发阶段追踪和调试代码&#xff0c;还在软件运行阶段提供了宝贵的运行信息和错误追踪能力。随着软件系统的日益复杂&#xff0c;对日志记录的需求也变得越来越高…

8.3 End-to-end Data Protection (Optional)

8.3 End-to-end Data Protection (Optional) 为了提供从应用程序到NVM介质并返回到应用程序本身的稳健数据保护,可以使用端到端数据保护。如果启用了此可选机制,则将额外的保护信息(例如CRC)添加到逻辑块中,控制器和/或主机软件可以对其进行评估,以确定逻辑块的完整性。…

大模型能干什么

大模型是指具有庞大参数量和复杂结构的机器学习模型。相比于传统的小模型&#xff0c;大模型通常具有更强的表达能力和更高的预测准确性&#xff0c;其在机器学习和人工智能领域中扮演着至关重要的角色&#xff0c;它们能够处理大规模数据和复杂模型&#xff0c;具有广泛的应用…

一文带你读懂MLIR论文,理解MLIR设计准则.

论文MLIR: Scaling Compiler Infrastructure for Domain Specific Computation MLIR&#xff1a;针对特定领域计算扩展编译器基础设施 文章目录 论文MLIR: Scaling Compiler Infrastructure for Domain Specific Computation1. 论文下载2. TVM关于MLIR的讨论3. 论文正文0. 摘要…