C#实现跨进程条件变量的示例代码

 更新时间:2024年07月22日 10:22:56   作者:CodeOfCC  
C#提供的多进程同步对象有互斥锁和信号量,但是并没有条件变量,虽然信号量条件变量一定程度可以等效,但是具体的使用还是会有区别,本文提供了一种条件变量的实现方法,可以用于进程间的同步控制,需要的朋友可以参考下

前言

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();
    //共享进程读写略
    //通知进程B
    cv.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();
}

总结

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

到此这篇关于C#实现跨进程条件变量的示例代码的文章就介绍到这了,更多相关C#跨进程条件变量内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • C#使用NPOI操作Excel工具类的实现

    C#使用NPOI操作Excel工具类的实现

    NPOI是POI项目的.NET迁移版本,使用NPOI可以在没有安装Office或者相应环境的机器上对Word或Excel文档进行读写操作,下面我们就来学习一下如何使用NPOI编写操作Excel的工具类吧
    2023-11-11
  • C#中的out参数、ref参数和params可变参数用法介绍

    C#中的out参数、ref参数和params可变参数用法介绍

    这篇文章介绍了C#中的out参数、ref参数和params可变参数用法,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-01-01
  • C#中如何获取文件图标

    C#中如何获取文件图标

    这篇文章主要介绍了C#中如何获取文件图标的相关资料,需要的朋友可以参考下
    2016-03-03
  • C#实现简单的飞行棋小游戏

    C#实现简单的飞行棋小游戏

    这篇文章主要为大家详细介绍了C#实现简单的飞行棋小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下<BR>
    2021-11-11
  • C#实现将日志写入文本文件的方法

    C#实现将日志写入文本文件的方法

    这篇文章主要介绍了C#实现将日志写入文本文件的方法,涉及C#针对日志文件写入的相关技巧,需要的朋友可以参考下
    2015-05-05
  • C#实现将音频PCM数据封装成wav文件

    C#实现将音频PCM数据封装成wav文件

    这篇文章主要为大家详细介绍了C#如何实现将音频PCM数据封装成wav文件,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2023-10-10
  • DataGridView带图标的单元格实现代码

    DataGridView带图标的单元格实现代码

    这篇文章主要为大家详细介绍了DataGridView带图标的单元格的实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-08-08
  • C#中IList<T>与List<T>的区别深入解析

    C#中IList<T>与List<T>的区别深入解析

    本篇文章主要是对C#中IList<T>与List<T>的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
    2014-01-01
  • unity实现无限列表功能

    unity实现无限列表功能

    这篇文章主要介绍了unity实现无限列表功能,水平方向,竖直方向滑动,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-04-04
  • 使用C#发送Http请求实现模拟登陆实例

    使用C#发送Http请求实现模拟登陆实例

    本文主要介绍了使用C#发送Http请求实现模拟登陆实例,模拟登陆的原理简单,想要了解的朋友可以了解一下。
    2016-10-10

最新评论