C#多线程使用读写锁ReaderWriterLockSlim同步写入文件


C#多线程使用读写锁ReaderWriterLockSlim同步写入文件

复杂多线程环境下使用读写锁同步写入文件

代码一:

   class Program
    {
        static int LogCount = 1000;
        static int SumLogCount = 0;
        static int WritedCount = 0;
        static int FailedCount = 0;
        static void Main(string[] args)
        {
            //往线程池里添加一个任务,迭代写入N个日志
            SumLogCount += LogCount;
            ThreadPool.QueueUserWorkItem((obj) =>
            {
                Parallel.For(0, LogCount, e =>
                {
                    WriteLog();
                });
            });
            //在新的线程里,添加N个写入日志的任务到线程池
            SumLogCount += LogCount;
            var thread1 = new Thread(() =>
            {
                Parallel.For(0, LogCount, e =>
                {
                    ThreadPool.QueueUserWorkItem((subObj) =>
                    {
                        WriteLog();
                    });
                });
            });
            thread1.IsBackground = false;
            thread1.Start();
            //添加N个写入日志的任务到线程池
            SumLogCount += LogCount;
            Parallel.For(0, LogCount, e =>
            {
                ThreadPool.QueueUserWorkItem((obj) =>
                {
                    WriteLog();
                });
            });
            //在新的线程里,迭代写入N个日志
            SumLogCount += LogCount;
            var thread2 = new Thread(() =>
            {
                Parallel.For(0, LogCount, e =>
                {
                    WriteLog();
                });
            });
            thread2.IsBackground = false;
            thread2.Start();
            //在当前线程里,迭代写入N个日志
            SumLogCount += LogCount;
            Parallel.For(0, LogCount, e =>
            {
                WriteLog();
            });
            Console.WriteLine("Main Thread Processed.\r\n");

            while (true)
            {
                Console.WriteLine(string.Format("Sum Log Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", SumLogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));
                Console.ReadLine();
            }
        }
        //读写锁,当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入
        static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim();
        static void WriteLog()
        {
            try
            {
                //设置读写锁为写入模式独占资源,其他写入请求需要等待本次写入结束之后才能继续写入
                //注意:长时间持有读线程锁或写线程锁会使其他线程发生饥饿 (starve)。 为了得到最好的性能,需要考虑重新构造应用程序以将写访问的持续时间减少到最小。
                //从性能方面考虑,请求进入写入模式应该紧跟文件操作之前,在此处进入写入模式仅是为了降低代码复杂度
                //因进入与退出写入模式应在同一个try finally语句块内,所以在请求进入写入模式之前不能触发异常,否则释放次数大于请求次数将会触发异常
                LogWriteLock.EnterWriteLock();
                var logFilePath = "log.txt";
                var now = DateTime.Now;
                var logContent = string.Format("Tid: {0}{1} {2}.{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), now.Millisecond.ToString());
                File.AppendAllText(logFilePath, logContent);
                WritedCount++;
            }
            catch (Exception)
            {
                FailedCount++;
            }
            finally
            {
                //退出写入模式,释放资源占用
                //注意:一次请求对应一次释放
                // 若释放次数大于请求次数将会触发异常[写入锁定未经保持即被释放]
                // 若请求处理完成后未释放将会触发异常[此模式不下允许以递归方式获取写入锁定]
                LogWriteLock.ExitWriteLock();
            }
        }
    }

运行结果:

复杂多线程环境下使用读写锁,全部日志成功写入了日志文件,由ThreadId和DateTime可以看出是由不同的线程同步写入。

代码二:

   class Program
    {
        static void Main(string[] args)
        {
            #region 简单使用
            //var mutexKey = MutexExample.GetFilePathMutexKey("文件路径");
            //MutexExample.MutexExec(mutexKey, () =>
            //{
            //    Console.WriteLine("需要进程同步执行的代码");
            //});
            #endregion

            #region 测试代码
            //D:\Winform\多线程\多線程_互斥信號量\bin\Debug\TEST.LOG
            var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "test.log").ToUpper();
            var mutexKey = MutexExample.GetFilePathMutexKey(filePath);

            //同时开启N个写入线程
            Parallel.For(0, LogCount, e =>
            {
                //没使用互斥锁操作写入,大量写入错误;FileStream包含FileShare的构造函数也仅实现了进程内的线程同步,多进程同时写入时也会出错
                //WriteLog(filePath);

                //使用互斥锁操作写入,由于同一时间仅有一个线程操作,所以不会出错
                MutexExample.MutexExec(mutexKey, () =>
                {
                    WriteLog(filePath);
                });
            });

            Console.WriteLine(string.Format("Log Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));
            Console.Read();
            #endregion
        }


        /// <summary>
        /// C#互斥量使用示例代码
        /// </summary>
        /// <remarks>已在经过测试并上线运行,可直接使用</remarks>
        public static class MutexExample
        {
            /// <summary>
            /// 进程间同步执行的简单例子
            /// </summary>
            /// <param name="action">同步处理代码</param>
            /// <param name="mutexKey">操作系统级的同步键
            /// (如果将 name 指定为 null 或空字符串,则创建一个局部互斥体。 
            /// 如果名称以前缀“Global\”开头,则 mutex 在所有终端服务器会话中均为可见。 
            /// 如果名称以前缀“Local\”开头,则 mutex 仅在创建它的终端服务器会话中可见。 
            /// 如果创建已命名 mutex 时不指定前缀,则它将采用前缀“Local\”。)</param>
            /// <remarks>不重试且不考虑异常情况处理的简单例子</remarks>
            [Obsolete(error: false, message: "请使用MutexExec")]
            public static void MutexExecEasy(string mutexKey, Action action)
            {
                //声明一个已命名的互斥体,实现进程间同步;该命名互斥体不存在则自动创建,已存在则直接获取
                using (Mutex mut = new Mutex(false, mutexKey))
                {
                    try
                    {
                        //上锁,其他线程需等待释放锁之后才能执行处理;若其他线程已经上锁或优先上锁,则先等待其他线程执行完毕
                        mut.WaitOne();
                        //执行处理代码(在调用WaitHandle.WaitOne至WaitHandle.ReleaseMutex的时间段里,只有一个线程处理,其他线程都得等待释放锁后才能执行该代码段)
                        action();
                    }
                    finally
                    {
                        //释放锁,让其他进程(或线程)得以继续执行
                        mut.ReleaseMutex();
                    }
                }
            }


            /// <summary>
            /// 获取文件名对应的进程同步键
            /// </summary>
            /// <param name="filePath">文件路径(请注意大小写及空格)</param>
            /// <returns>进程同步键(互斥体名称)</returns>
            public static string GetFilePathMutexKey(string filePath)
            {
                //生成文件对应的同步键,可自定义格式(互斥体名称对特殊字符支持不友好,遂转换为BASE64格式字符串)
                var fileKey = Convert.ToBase64String(Encoding.Default.GetBytes(string.Format(@"FILE\{0}", filePath)));
                //转换为操作系统级的同步键
                var mutexKey = string.Format(@"Global\{0}", fileKey);
                return mutexKey;
            }

            /// <summary>
            /// 进程间同步执行
            /// </summary>
            /// <param name="mutexKey">操作系统级的同步键
            /// (如果将 name 指定为 null 或空字符串,则创建一个局部互斥体。 
            /// 如果名称以前缀“Global\”开头,则 mutex 在所有终端服务器会话中均为可见。 
            /// 如果名称以前缀“Local\”开头,则 mutex 仅在创建它的终端服务器会话中可见。 
            /// 如果创建已命名 mutex 时不指定前缀,则它将采用前缀“Local\”。)</param>
            /// <param name="action">同步处理操作</param>
            public static void MutexExec(string mutexKey, Action action)
            {
                MutexExec(mutexKey: mutexKey, action: action, recursive: false);
            }

            /// <summary>
            /// 进程间同步执行
            /// </summary>
            /// <param name="mutexKey">操作系统级的同步键
            /// (如果将 name 指定为 null 或空字符串,则创建一个局部互斥体。 
            /// 如果名称以前缀“Global\”开头,则 mutex 在所有终端服务器会话中均为可见。 
            /// 如果名称以前缀“Local\”开头,则 mutex 仅在创建它的终端服务器会话中可见。 
            /// 如果创建已命名 mutex 时不指定前缀,则它将采用前缀“Local\”。)</param>
            /// <param name="action">同步处理操作</param>
            /// <param name="recursive">指示当前调用是否为递归处理,递归处理时检测到异常则抛出异常,避免进入无限递归</param>
            private static void MutexExec(string mutexKey, Action action, bool recursive)
            {
                //声明一个已命名的互斥体,实现进程间同步;该命名互斥体不存在则自动创建,已存在则直接获取
                //initiallyOwned: false:默认当前线程并不拥有已存在互斥体的所属权,即默认本线程并非为首次创建该命名互斥体的线程
                //注意:并发声明同名的命名互斥体时,若间隔时间过短,则可能同时声明了多个名称相同的互斥体,并且同名的多个互斥体之间并不同步,高并发用户请另行处理
                using (Mutex mut = new Mutex(initiallyOwned: false, name: mutexKey))
                {
                    try
                    {
                        //上锁,其他线程需等待释放锁之后才能执行处理;若其他线程已经上锁或优先上锁,则先等待其他线程执行完毕
                        mut.WaitOne();
                        //执行处理代码(在调用WaitHandle.WaitOne至WaitHandle.ReleaseMutex的时间段里,只有一个线程处理,其他线程都得等待释放锁后才能执行该代码段)
                        action();
                    }
                    //当其他进程已上锁且没有正常释放互斥锁时(譬如进程忽然关闭或退出),则会抛出AbandonedMutexException异常
                    catch (AbandonedMutexException ex)
                    {
                        //避免进入无限递归
                        if (recursive)
                            throw ex;

                        //非递归调用,由其他进程抛出互斥锁解锁异常时,重试执行
                        MutexExec(mutexKey: mutexKey, action: action, recursive: true);
                    }
                    finally
                    {
                        //释放锁,让其他进程(或线程)得以继续执行
                        mut.ReleaseMutex();
                    }
                }
            }
        }


        #region 测试写文件的代码
        static int LogCount = 500;
        static int WritedCount = 0;
        static int FailedCount = 0;
        static void WriteLog(string logFilePath)
        {
            try
            {
                var now = DateTime.Now;
                var logContent = string.Format("Tid: {0}{1} {2}.{3}\r\n", Thread.CurrentThread.ManagedThreadId.ToString().PadRight(4), now.ToLongDateString(), now.ToLongTimeString(), now.Millisecond.ToString());
                File.AppendAllText(logFilePath, logContent);
                WritedCount++;
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                FailedCount++;
            }
        }
        #endregion
    }

运行结果:

 

c/s架构winform开发框架

版权声明:本文为开发框架文库发布内容,转载请附上原文出处连接
C/S框架网
上一篇:C# LINQ使用案例参考
下一篇:C#直接使用DllImport外部Dll的方法
评论列表

发表评论

评论内容
昵称:
关联文章

C#线使用ReaderWriterLockSlim同步写入文件
C#.Net使用线池(ThreadPool)与专用线(Thread)
C# 线入门 - 开发实例
C#异步编程(线
C#线处理个队列的数据(交叉线访问及Invoke方法使用)
C#.NET7 线播放mp3/wav音频|winmm.dll winapi播放音乐文件
C#,Asp.Net线断点续传下载
C# INI本地文件工具类IniFileTool.cs
C#异步操作等待窗体,异步线处理数据通用界面(frmThreadOperating)
C#线异步处理数据通用界面窗体(frmThreadOperating)
DataLock - 数据、单据号码、并发C#源码)
尝试释放正在使用的RCW,活动线或其他线上正在使用该RCW
.NET5 HttpClient线并发请求阻塞“发生一个或个错误”解决方案
C#.Net前台线与后台线的区别
编译器错误消息: CS0016: 未能写入输出文件c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\root
C#序列化/反序列化对象.将对象写入文件,从文件读取对象.
C# 设置文件目录只属性,取消只属性
SQL死的分析与解决(转)
C# OOP编程 模拟做早餐探索同步异步机制
C#操作Excel合并个Excel文件

热门标签
.NET5 .NET6 .NET7 APP Auth-软件授权注册系统 Axios B/S B/S开发框架 Bug Bug记录 C#加密解密 C#源码 C/S CHATGPT CMS系统 CodeGenerator CSFramework.DB CSFramework.EF CSFrameworkV1学习版 CSFrameworkV2标准版 CSFrameworkV3高级版 CSFrameworkV4企业版 CSFrameworkV5旗舰版 CSFrameworkV6.0 DAL数据访问层 Database datalock DbFramework Demo教学 Demo下载 DevExpress教程 DOM EF框架 Element-UI EntityFramework ERP ES6 Excel FastReport GIT HR IDatabase IIS JavaScript LINQ MES MiniFramework MIS NavBarControl Node.JS NPM OMS ORM PaaS POS Promise API Redis SAP SEO SQL SQLConnector TMS系统 Token令牌 VS2022 VSCode VUE WCF WebApi WebApi NETCore WebApi框架 WEB开发框架 Windows服务 Winform 开发框架 Winform 开发平台 WinFramework Workflow工作流 Workflow流程引擎 版本区别 报表 踩坑日记 操作手册 代码生成器 迭代开发记录 基础资料窗体 架构设计 角色权限 开发sce 开发技巧 开发教程 开发框架 开发平台 开发指南 客户案例 快速搭站系统 快速开发平台 秘钥 密钥 权限设计 软件报价 软件测试报告 软件简介 软件开发框架 软件开发平台 软件开发文档 软件体系架构 软件下载 软著证书 三层架构 设计模式 生成代码 实用小技巧 收钱音箱 数据锁 数据同步 微信小程序 未解决问题 文档下载 喜鹊ERP 喜鹊软件 系统对接 详细设计说明书 行政区域数据库 需求分析 疑难杂症 蝇量级框架 蝇量框架 用户管理 用户开发手册 用户控件 在线支付 纸箱ERP 智能语音收款机 自定义窗体 自定义组件 自动升级程序