本文主要是实现操作系统级别的多进程间线程同步(进程同步)的示例代码及测试结果。代码经过测试,可供参考,也可直接使用。

承接上一篇博客的业务场景[C#使用读写锁三行代码简单解决多线程并发写入文件时线程同步的问题]。

随着服务进程的增多,光凭进程内的线程同步已经不能满足现在的需求,导致多进程同时写入同一个文件时,一样提示文件被占用的问题。

在这种场景下,跨进程级的锁是不可避免的。在.NET提供的参考中,进程锁都继承了System.Threading.WaitHandle类。

而在本文中针对单个文件同一时间仅允许单个进程(线程)操作的场景,System.Threading.Mutex类无疑是最简单也是最合适的选择。

该类型的对象可以使用命名(字符串)互斥量实现当前会话级或操作系统级的同步需求。我选择了操作系统级别的同步编写示例,因为覆盖面更广。

下面是实现代码,注释很详细就不细说了:

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

测试不使用进程同步,多进程多线程同时写入文件:

测试结果:6个进程同时进行3000次写入请求,仅成功写入277次

测试使用互斥量进行进程同步,多进程多线程同时写入文件:

测试结果:6个进程同时进行3000次写入请求,全部成功写入

补充:

进程同步的资源消耗及效率比线程同步要差得多,请根据实际场景合理使用。

本文虽然是用写入文件作为示例,但进程同步的代码使用场景与文件操作无关。

Semaphore类(信号灯)虽然可以限制同时操作的线程数,甚至把最大同时操作数设置为1时,行为与Mutex类(互斥量)类似;但是由于信号灯在其他进程中出现异常退出时并不能接收到异常通知,只能通过等待超时触发异常,并不适合现在的场景,所以并没讲述。

关于进程同步的其他深入了解及应用,请参阅其他资料。

C#多进程文件读写的锁处理相关推荐

  1. Ext2文件系统—文件读写

    1.定义 只有在"打开"了文件以后,或者说建立了进程与文件的"连接"之后,才能对文件进行读写.为了提高效率,Linux的读写操作都是带缓冲的,即写的时候先写到缓 ...

  2. python多线程读取文件的问题_Python多线程同步---文件读写控制方法

    1.实现文件读写的文件ltz_schedule_times.py #! /usr/bin/env python #coding=utf-8 import os def ReadTimes(): res ...

  3. linux存储--从内核文件系统看文件读写过程(四)

    系统调用 操作系统的主要功能是为管理硬件资源和为应用程序开发人员提供良好的环境,但是计算机系统的各种硬件资源是有限的,因此为了保证每一个进程都能安全的执行.处理器设有两种模式:"用户模式&q ...

  4. 转 从内核文件系统看文件读写过程

    系统调用 操作系统的主要功能是为管理硬件资源和为应用程序开发人员提供良好的环境,但是计算机系统的各种硬件资源是有限的,因此为了保证每一个进程都能安全的执行.处理器设有两种模式:"用户模式&q ...

  5. liunx内核中的互斥自旋锁和读写自旋锁的实现详解

    今天把这两个锁的内核实现源码重新捋了一遍,基于liunx2,6.0,直接粘注释版: 核心文件,x86下实现的spinlock #ifndef __ASM_SPINLOCK_H #define __AS ...

  6. 从内核文件系统看文件读写过程

    阅读目录 系统调用 虚拟文件系统 I/O 缓冲区 Page Cache Address Space 文件读写基本流程 系统调用 操作系统的主要功能是为管理硬件资源和为应用程序开发人员提供良好的环境,但 ...

  7. 网络编程(part3)--文件读写之二进制文件读写/with操作

    鄙人学习笔记 文章目录 二进制文件读写 举个例子1(以二进制方式打开并读取txt文件) 举个例子2(以二进制方式打开并读取图片) 举个例子3(二进制文件打开并写入操作) 关闭文件 with操作 wit ...

  8. linux gcc编译下的文件读写操作

    linux下的文件操作 所有目录             1.文件及文件系统的定义             2.linux文件的类型             3.linux文件的权限          ...

  9. pom.xml文件第一行报错_Python文件读写指南

    作者:豌豆花下猫,某985高校毕业生, 兼具极客思维与人文情怀 .公众号[Python猫], 专注python技术.数据科学和深度学习,力图创造一个有趣又有用的学习分享平台. 对于初学者来说,一份详尽 ...

  10. Python 3 并发编程多进程之进程同步(锁)

    Python 3 并发编程多进程之进程同步(锁) 进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,竞争带来的结果就是错乱,如何控制,就是加锁处理. 1. ...

最新文章

  1. DLL: 一种直接法的激光雷达定位方案
  2. 《Programming WPF》翻译 目录
  3. php中的unlink(),unset(),rmdir()删除函数
  4. 2016年3月12日广州开源社区巡讲活动
  5. mac 打开html c python,在Mac系统使用Visual Studio Code运行Python的方法
  6. HDU1058 Humble Numbers
  7. 一切都是关于“ –ilities”的
  8. 传统认知PK网络认知 刚子扯谈烤串认知
  9. 我在美团的八年,技术人必读
  10. 学python可以从事什么工作-学Python可以找什么工作或者做什么兼职?
  11. 使用tar或dd等完成Linux系统备份恢复
  12. Macs Fan Control Pro for mac(电脑风扇控制软件)v1.5.10中文
  13. 策略模式 (Strategy)
  14. 曼昆微观经济学--十大原理
  15. RedHat7安装yum并下载gcc
  16. 在SOLIDWORKS中如何建立基准面
  17. 指数函数误差平方和matlab,数值分析与实验数学081 张燃 3080801119).doc
  18. 存储卡中各种文件夹用处
  19. stty 命令中文使用详解
  20. 腾讯微博android版本,腾讯微博 Android版

热门文章

  1. 化繁为简的终极指南化繁为简的终极指南
  2. pwnable - mistake
  3. 带有en的单词有哪些_英语前缀大全en:开头是EN的单词有哪些
  4. dis的前缀单词有哪些_dis前缀的英语单词
  5. 自定义view————涂鸦画板
  6. 计算机专业毕业设计题目哪个简单,计算机专业毕业设计题目计算机专业毕业设计的类型...
  7. office Word中手动添加MathType插件
  8. win10桌面右键一直转圈_吐血推荐:掌握这些技巧,win10工作效率提升9999%
  9. 2. Zigbee应用程序框架开发指南 - 应用程序框架结构
  10. 计算机怎么设置加密文件,电脑文件夹怎么设置密码 电脑文件夹加密的3种方法...