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 依次点击部署中心----部署组件----物理/虚拟机部署----选择集群----下一步 选择手动部署-…

消费金融系统开发回忆录

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

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

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

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;链表的结构一共有八种&#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位。…

一文带你读懂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. 摘要…

02互联网行业的产品方向(2)

数字与策略产品 大数据时代&#xff0c;数据的价值越来越重要。大多数公司开始对内外全部数据进行管理与挖掘&#xff0c;将业务数据化&#xff0c;数据资产化&#xff0c;资产业务化&#xff0c;将数据产品赋能业务&#xff0c;通过数据驱动公司业务发展&#xff0c;支撑公司战…

Unity VR开发入门:探索虚拟现实世界的无限可能

目录 引言 Unity VR开发基础 1. 安装Unity与VR SDK 2. 创建VR项目 3. 理解VR场景结构 Unity VR开发实战 1. 场景搭建 2. 交互设计 创建C#脚本 编写VRInteractor脚本 应用脚本到场景 注意 修改VRInteractor脚本 3. 用户体验优化 4. 测试与调试 引言 随着科技的飞速…

docker: No space left on device处理与迁移目录

简介&#xff1a;工作中当遇到Docker容器内部的磁盘空间已满。可能的原因包括日志文件过大、临时文件过多或者是Docker容器的存储卷已满&#xff0c;需要我们及时清理相关文件&#xff0c;并对docker的路径进行迁移。 历史攻略&#xff1a; centos&#xff1a;清理磁盘空间 …

记录些MySQL题集(17)

一、MySQL索引为何使用B树结构&#xff1f; MySQL的索引机制中&#xff0c;默认使用BTree作为底层的数据结构&#xff0c;但为什么要选择B树呢&#xff1f;有人会说树结构是以二分法查找数据&#xff0c;所以会在很大程度上提升检索性能&#xff0c;这点确实没错&#xff0c;但…

C++初学者指南-5.标准库(第一部分)--标准库查询存在算法

C初学者指南-5.标准库(第一部分)–标准库查询存在算法 文章目录 C初学者指南-5.标准库(第一部分)--标准库查询存在算法any_of / all_of / none_ofcountcount_if相关内容 不熟悉 C 的标准库算法&#xff1f; ⇒ 简介 any_of / all_of / none_of 如果在输入范围(所有元素…

解决django与sqlite3不兼容报SQLite 3.9.0 or later is required错的问题

今天在尝试用pytest进行django的单元测试&#xff0c;pytest用的数据库是sqlite3&#xff0c;在window环境下测试得好好的&#xff0c;但是放到linux环境下就报错&#xff0c;具体是报django.core.exceptions.ImproperlyConfigured: SQLite 3.9.0 or later is required (found …