• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >C#教程 > C#使用读写锁三行代码简单解决多线程并发的问题

C#使用读写锁三行代码简单解决多线程并发的问题

作者:弎吩锺熱℃ 字体:[增加 减小] 来源:互联网 时间:2017-05-28

弎吩锺熱℃ 通过本文主要向大家介绍了等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

在开发程序的过程中,难免少不了写入错误日志这个关键功能。实现这个功能,可以选择使用第三方日志插件,也可以选择使用数据库,还可以自己写个简单的方法把错误信息记录到日志文件。

选择最后一种方法实现的时候,若对文件操作与线程同步不熟悉,问题就有可能出现了,因为同一个文件并不允许多个线程同时写入,否则会提示“文件正在由另一进程使用,因此该进程无法访问此文件”。

这是文件的并发写入问题,就需要用到线程同步。而微软也给线程同步提供了一些相关的类可以达到这样的目的,本文使用到的 System.Threading.ReaderWriterLockSlim 便是其中之一。

该类用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问。利用这个类,我们就可以避免在同一时间段内多线程同时写入一个文件而导致的并发写入问题。

读写锁是以 ReaderWriterLockSlim 对象作为锁管理资源的,不同的 ReaderWriterLockSlim 对象中锁定同一个文件也会被视为不同的锁进行管理,这种差异可能会再次导致文件的并发写入问题,所以 ReaderWriterLockSlim 应尽量定义为只读的静态对象。

ReaderWriterLockSlim 有几个关键的方法,本文仅讨论写入锁:

调用 EnterWriteLock 方法 进入写入状态,在调用线程进入锁定状态之前一直处于阻塞状态,因此可能永远都不返回。

调用 TryEnterWriteLock 方法 进入写入状态,可指定阻塞的间隔时间,如果调用线程在此间隔期间并未进入写入模式,将返回false。

调用 ExitWriteLock 方法 退出写入状态,应使用 finally 块执行 ExitWriteLock 方法,从而确保调用方退出写入模式。

Don't talk, show me the code.

1.多线程同时写入文件

class Program
 {
 static int LogCount = 100;
 static int WritedCount = 0;
 static int FailedCount = 0;
 static void Main(string[] args)
 {
 //迭代运行写入日志记录,由于多个线程同时写入同一个文件将会导致错误
 Parallel.For(0, LogCount, e =>
 {
 WriteLog();
 });
 Console.WriteLine(string.Format("\r\nLog Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));
 Console.Read();
 }
 static void WriteLog()
 {
 try
 {
 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 ex)
 {
 FailedCount++;
 Console.WriteLine(ex.Message);
 }
 }
 }
</div>

运行结果:

不使用读写锁,只有部分日志成功写入了日志文件。

2.多线程使用读写锁同步写入文件

class Program
 {
 static int LogCount = 100;
 static int WritedCount = 0;
 static int FailedCount = 0;
 static void Main(string[] args)
 {
 //迭代运行写入日志记录
 Parallel.For(0, LogCount, e =>
 {
 WriteLog();
 });
 Console.WriteLine(string.Format("\r\nLog Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString()));
 Console.Read();
 }
 //读写锁,当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入
 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();
 }
 }
 }
</div>

运行结果:

使用读写锁,全部日志成功写入了日志文件。

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

 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();
 }
 }
 }
</div>

运行结果:

部分日志文件

分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

相关文章

  • 2017-05-28基于C#实现12306的动态验证码变成静态验证码的方法
  • 2017-05-28DevExpress SplitContainerControl用法总结
  • 2017-05-28C#线程池操作方法
  • 2017-05-28C#实现压缩HTML代码的方法
  • 2017-05-28c#删除代码中的单行注释行示例
  • 2017-05-28C# DES加密算法中向量的作用详细解析
  • 2017-05-28C#遍历系统进程的方法
  • 2017-05-28使用C#开发ActiveX控件
  • 2017-05-28C#操作session的类实例
  • 2017-05-28在Winform动态启动、控制台命令行的方法

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • c#后台线程访问前台控件并显示信息示例
    • C#中datagridview的EditingControlShowing事件用法实例
    • C#解码base64编码二进制数据的方法
    • C# 设计模式系列教程-抽象工厂模式
    • 新手学习.net的一列好走的路径及方法
    • 让C# Excel导入导出 支持不同版本Office
    • C#使用NPOI导入Excel的方法详解
    • webBrowser代理设置c#代码
    • C#开发Android百度地图手机应用程序(多地图展示)
    • c#取得控制台应用程序根目录

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有