C#使用读写锁解决多线程并发问题
作者:農碼一生
一、简介
在开发程序的过程中,难免少不了写入错误日志这个关键功能。实现这个功能,可以选择使用第三方日志插件,也可以选择使用数据库,还可以自己写个简单的方法把错误信息记录到日志文件。现在我们来讲下最后一种方法:
在选择最后一种方法实现的时候,若对文件操作与线程同步不熟悉,问题就有可能出现了,因为同一个文件并不允许多个线程同时写入,否则会提示“文件正在由另一进程使用,因此该进程无法访问此文件”。这是文件的并发写入问题,就需要用到线程同步。而微软也给线程同步提供了一些相关的类可以达到这样的目的,本文使用到的 System.Threading.ReaderWriterLockSlim 便是其中之一。该类用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问。利用这个类,我们就可以避免在同一时间段内多线程同时写入一个文件而导致的并发写入问题。读写锁是以 ReaderWriterLockSlim 对象作为锁管理资源的,不同的 ReaderWriterLockSlim 对象中锁定同一个文件也会被视为不同的锁进行管理,这种差异可能会再次导致文件的并发写入问题,所以 ReaderWriterLockSlim 应尽量定义为只读的静态对象。
ReaderWriterLockSlim 有几个关键的方法,本文仅讨论写入锁:
1.调用 EnterWriteLock 方法 进入写入状态,在调用线程进入锁定状态之前一直处于阻塞状态,因此可能永远都不返回。
2.调用 TryEnterWriteLock 方法 进入写入状态,可指定阻塞的间隔时间,如果调用线程在此间隔期间并未进入写入模式,将返回false。
3.调用 ExitWriteLock 方法 退出写入状态,应使用 finally 块执行 ExitWriteLock 方法,从而确保调用方退出写入模式。
二、不使用读写锁写入文件:
代码:
class Program { static int LogCount = 100; static int WritedCount = 0; static int FailedCount = 0; static void Main(string[] args) { //迭代运行写入日志记录,由于多个线程同时写入同一个文件将会导致错误 Parallel.For(0, LogCount, e => { WriteLog1(); }); Console.WriteLine(string.Format("\r\nLog Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString())); Console.Read(); } #region 未加入读写锁 //不使用读写锁写入文件 static void WriteLog1() { 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); } } #endregion }
运行结果:
不是所有的log都能写入到log.txt,因为不适用读写错可能会出现上面提到的:“文件正在由另一进程使用,因此该进程无法访问此文件”报错信息。
记录log信息:
Tid: 9 2021年5月21日 下午 02:18:04.919 Tid: 9 2021年5月21日 下午 02:18:04.944 Tid: 9 2021年5月21日 下午 02:18:05.80 Tid: 11 2021年5月21日 下午 02:18:05.81 Tid: 9 2021年5月21日 下午 02:18:05.82 Tid: 12 2021年5月21日 下午 02:18:05.83 Tid: 11 2021年5月21日 下午 02:18:05.84 Tid: 12 2021年5月21日 下午 02:18:05.84 Tid: 16 2021年5月21日 下午 02:18:05.85 Tid: 12 2021年5月21日 下午 02:18:05.111 Tid: 16 2021年5月21日 下午 02:18:05.117 Tid: 16 2021年5月21日 下午 02:18:05.128 Tid: 11 2021年5月21日 下午 02:18:05.128 Tid: 16 2021年5月21日 下午 02:18:05.133 Tid: 12 2021年5月21日 下午 02:18:05.138 Tid: 16 2021年5月21日 下午 02:18:05.140 Tid: 12 2021年5月21日 下午 02:18:05.140 Tid: 16 2021年5月21日 下午 02:18:05.142 Tid: 16 2021年5月21日 下午 02:18:05.144 Tid: 16 2021年5月21日 下午 02:18:05.151 Tid: 16 2021年5月21日 下午 02:18:05.158 Tid: 9 2021年5月21日 下午 02:18:05.159 Tid: 10 2021年5月21日 下午 02:18:05.159 Tid: 9 2021年5月21日 下午 02:18:05.164 Tid: 16 2021年5月21日 下午 02:18:05.164 Tid: 9 2021年5月21日 下午 02:18:05.172 Tid: 15 2021年5月21日 下午 02:18:05.172 Tid: 16 2021年5月21日 下午 02:18:05.181 Tid: 16 2021年5月21日 下午 02:18:05.187 Tid: 15 2021年5月21日 下午 02:18:05.188 Tid: 16 2021年5月21日 下午 02:18:05.195 Tid: 16 2021年5月21日 下午 02:18:05.196 Tid: 15 2021年5月21日 下午 02:18:05.195 Tid: 16 2021年5月21日 下午 02:18:05.202 Tid: 16 2021年5月21日 下午 02:18:05.203 Tid: 15 2021年5月21日 下午 02:18:05.202 Tid: 15 2021年5月21日 下午 02:18:05.207 Tid: 15 2021年5月21日 下午 02:18:05.209 Tid: 16 2021年5月21日 下午 02:18:05.207 Tid: 15 2021年5月21日 下午 02:18:05.210 Tid: 15 2021年5月21日 下午 02:18:05.222 Tid: 15 2021年5月21日 下午 02:18:05.231 Tid: 18 2021年5月21日 下午 02:18:05.238 Tid: 15 2021年5月21日 下午 02:18:05.238 Tid: 18 2021年5月21日 下午 02:18:05.244 Tid: 15 2021年5月21日 下午 02:18:05.251 Tid: 15 2021年5月21日 下午 02:18:05.256 Tid: 15 2021年5月21日 下午 02:18:05.262 Tid: 15 2021年5月21日 下午 02:18:05.304 Tid: 15 2021年5月21日 下午 02:18:05.312 Tid: 13 2021年5月21日 下午 02:18:05.312 Tid: 9 2021年5月21日 下午 02:18:05.313 Tid: 13 2021年5月21日 下午 02:18:05.320 Tid: 19 2021年5月21日 下午 02:18:05.320 Tid: 16 2021年5月21日 下午 02:18:05.325 Tid: 19 2021年5月21日 下午 02:18:05.333 Tid: 16 2021年5月21日 下午 02:18:05.342 Tid: 16 2021年5月21日 下午 02:18:05.349 Tid: 16 2021年5月21日 下午 02:18:05.361 Tid: 16 2021年5月21日 下午 02:18:05.366 Tid: 16 2021年5月21日 下午 02:18:05.367 Tid: 16 2021年5月21日 下午 02:18:05.368 Tid: 16 2021年5月21日 下午 02:18:05.376 Tid: 16 2021年5月21日 下午 02:18:05.386 Tid: 16 2021年5月21日 下午 02:18:05.392 Tid: 16 2021年5月21日 下午 02:18:05.401 Tid: 9 2021年5月21日 下午 02:18:05.463 Tid: 13 2021年5月21日 下午 02:18:05.464 Tid: 15 2021年5月21日 下午 02:18:05.464 Tid: 13 2021年5月21日 下午 02:18:05.465 Tid: 13 2021年5月21日 下午 02:18:05.470 Tid: 11 2021年5月21日 下午 02:18:05.479
三、使用读写锁写入文件:
代码:
class Program { static int LogCount = 100; static int WritedCount = 0; static int FailedCount = 0; static void Main(string[] args) { //迭代运行写入日志记录,由于多个线程同时写入同一个文件将会导致错误 Parallel.For(0, LogCount, e => { WriteLog2(); }); Console.WriteLine(string.Format("\r\nLog Count:{0}.\t\tWrited Count:{1}.\tFailed Count:{2}.", LogCount.ToString(), WritedCount.ToString(), FailedCount.ToString())); Console.Read(); } #region 加入读写锁 //读写锁,当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入 static ReaderWriterLockSlim LogWriteLock = new ReaderWriterLockSlim(); static void WriteLog2() { 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(); } } #endregion }
运行结果:
所有的log都完全正确写入到log.txt。
记录log信息:
Tid: 8 2021年5月21日 下午 02:26:36.573 Tid: 8 2021年5月21日 下午 02:26:36.597 Tid: 8 2021年5月21日 下午 02:26:36.599 Tid: 8 2021年5月21日 下午 02:26:36.600 Tid: 8 2021年5月21日 下午 02:26:36.601 Tid: 8 2021年5月21日 下午 02:26:36.602 Tid: 8 2021年5月21日 下午 02:26:36.608 Tid: 8 2021年5月21日 下午 02:26:36.609 Tid: 8 2021年5月21日 下午 02:26:36.614 Tid: 8 2021年5月21日 下午 02:26:36.616 Tid: 8 2021年5月21日 下午 02:26:36.617 Tid: 8 2021年5月21日 下午 02:26:36.620 Tid: 8 2021年5月21日 下午 02:26:36.620 Tid: 8 2021年5月21日 下午 02:26:36.621 Tid: 8 2021年5月21日 下午 02:26:36.622 Tid: 8 2021年5月21日 下午 02:26:36.623 Tid: 8 2021年5月21日 下午 02:26:36.624 Tid: 8 2021年5月21日 下午 02:26:36.624 Tid: 8 2021年5月21日 下午 02:26:36.625 Tid: 8 2021年5月21日 下午 02:26:36.626 Tid: 8 2021年5月21日 下午 02:26:36.626 Tid: 8 2021年5月21日 下午 02:26:36.627 Tid: 8 2021年5月21日 下午 02:26:36.628 Tid: 8 2021年5月21日 下午 02:26:36.628 Tid: 8 2021年5月21日 下午 02:26:36.629 Tid: 8 2021年5月21日 下午 02:26:36.630 Tid: 8 2021年5月21日 下午 02:26:36.630 Tid: 8 2021年5月21日 下午 02:26:36.631 Tid: 8 2021年5月21日 下午 02:26:36.632 Tid: 8 2021年5月21日 下午 02:26:36.632 Tid: 8 2021年5月21日 下午 02:26:36.633 Tid: 8 2021年5月21日 下午 02:26:36.634 Tid: 8 2021年5月21日 下午 02:26:36.634 Tid: 8 2021年5月21日 下午 02:26:36.635 Tid: 8 2021年5月21日 下午 02:26:36.636 Tid: 8 2021年5月21日 下午 02:26:36.636 Tid: 8 2021年5月21日 下午 02:26:36.637 Tid: 8 2021年5月21日 下午 02:26:36.638 Tid: 8 2021年5月21日 下午 02:26:36.638 Tid: 8 2021年5月21日 下午 02:26:36.639 Tid: 8 2021年5月21日 下午 02:26:36.641 Tid: 8 2021年5月21日 下午 02:26:36.641 Tid: 8 2021年5月21日 下午 02:26:36.642 Tid: 8 2021年5月21日 下午 02:26:36.643 Tid: 8 2021年5月21日 下午 02:26:36.644 Tid: 8 2021年5月21日 下午 02:26:36.644 Tid: 8 2021年5月21日 下午 02:26:36.645 Tid: 8 2021年5月21日 下午 02:26:36.646 Tid: 8 2021年5月21日 下午 02:26:36.647 Tid: 8 2021年5月21日 下午 02:26:36.647 Tid: 8 2021年5月21日 下午 02:26:36.648 Tid: 8 2021年5月21日 下午 02:26:36.649 Tid: 8 2021年5月21日 下午 02:26:36.650 Tid: 8 2021年5月21日 下午 02:26:36.650 Tid: 8 2021年5月21日 下午 02:26:36.651 Tid: 8 2021年5月21日 下午 02:26:36.652 Tid: 8 2021年5月21日 下午 02:26:36.652 Tid: 8 2021年5月21日 下午 02:26:36.652 Tid: 8 2021年5月21日 下午 02:26:36.653 Tid: 8 2021年5月21日 下午 02:26:36.654 Tid: 8 2021年5月21日 下午 02:26:36.655 Tid: 8 2021年5月21日 下午 02:26:36.656 Tid: 8 2021年5月21日 下午 02:26:36.658 Tid: 8 2021年5月21日 下午 02:26:36.658 Tid: 8 2021年5月21日 下午 02:26:36.659 Tid: 8 2021年5月21日 下午 02:26:36.660 Tid: 8 2021年5月21日 下午 02:26:36.660 Tid: 8 2021年5月21日 下午 02:26:36.661 Tid: 8 2021年5月21日 下午 02:26:36.662 Tid: 8 2021年5月21日 下午 02:26:36.662 Tid: 8 2021年5月21日 下午 02:26:36.663 Tid: 8 2021年5月21日 下午 02:26:36.664 Tid: 8 2021年5月21日 下午 02:26:36.664 Tid: 8 2021年5月21日 下午 02:26:36.665 Tid: 8 2021年5月21日 下午 02:26:36.666 Tid: 8 2021年5月21日 下午 02:26:36.666 Tid: 8 2021年5月21日 下午 02:26:36.667 Tid: 8 2021年5月21日 下午 02:26:36.668 Tid: 8 2021年5月21日 下午 02:26:36.669 Tid: 8 2021年5月21日 下午 02:26:36.669 Tid: 8 2021年5月21日 下午 02:26:36.670 Tid: 8 2021年5月21日 下午 02:26:36.671 Tid: 8 2021年5月21日 下午 02:26:36.672 Tid: 8 2021年5月21日 下午 02:26:36.673 Tid: 8 2021年5月21日 下午 02:26:36.675 Tid: 8 2021年5月21日 下午 02:26:36.675 Tid: 8 2021年5月21日 下午 02:26:36.676 Tid: 8 2021年5月21日 下午 02:26:36.677 Tid: 14 2021年5月21日 下午 02:26:36.678 Tid: 15 2021年5月21日 下午 02:26:36.679 Tid: 16 2021年5月21日 下午 02:26:36.680 Tid: 17 2021年5月21日 下午 02:26:36.681 Tid: 18 2021年5月21日 下午 02:26:36.681 Tid: 20 2021年5月21日 下午 02:26:36.683 Tid: 9 2021年5月21日 下午 02:26:36.683 Tid: 19 2021年5月21日 下午 02:26:36.684 Tid: 10 2021年5月21日 下午 02:26:36.685 Tid: 11 2021年5月21日 下午 02:26:36.685 Tid: 12 2021年5月21日 下午 02:26:36.687 Tid: 13 2021年5月21日 下午 02:26:36.688
到此这篇关于C#使用读写锁解决多线程并发问题的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持脚本之家。