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#使用读写锁解决多线程并发问题的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 如何使用C#读写锁ReaderWriterLockSlim

    读写锁的概念很简单,允许多个线程同时获取读锁,但同一时间只允许一个线程获得写锁,因此也称作共享-独占锁.在C#中,推荐使用ReaderWriterLockSlim类来完成读写锁的功能. 某些场合下,对一个对象的读取次数远远大于修改次数,如果只是简单的用lock方式加锁,则会影响读取的效率.而如果采用读写锁,则多个线程可以同时读取该对象,只有等到对象被写入锁占用的时候,才会阻塞. 简单的说,当某个线程进入读取模式时,此时其他线程依然能进入读取模式,假设此时一个线程要进入写入模式,那么他不得不被阻塞

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

    代码一: 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)

  • C#多线程系列之读写锁

    本篇的内容主要是介绍 ReaderWriterLockSlim 类,来实现多线程下的读写分离. ReaderWriterLockSlim ReaderWriterLock 类:定义支持单个写线程和多个读线程的锁. ReaderWriterLockSlim 类:表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问. 两者的 API 十分接近,而且 ReaderWriterLockSlim 相对 ReaderWriterLock 来说 更加安全.因此本文主要讲解 ReaderWrit

  • C#解决SQlite并发异常问题的方法(使用读写锁)

    本文实例讲述了C#解决SQlite并发异常问题的方法.分享给大家供大家参考,具体如下: 使用C#访问sqlite时,常会遇到多线程并发导致SQLITE数据库损坏的问题. SQLite是文件级别的数据库,其锁也是文件级别的:多个线程可以同时读,但是同时只能有一个线程写.Android提供了SqliteOpenHelper类,加入Java的锁机制以便调用.但在C#中未提供类似功能. 作者利用读写锁(ReaderWriterLock),达到了多线程安全访问的目标. using System; usin

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

    在开发程序的过程中,难免少不了写入错误日志这个关键功能.实现这个功能,可以选择使用第三方日志插件,也可以选择使用数据库,还可以自己写个简单的方法把错误信息记录到日志文件. 选择最后一种方法实现的时候,若对文件操作与线程同步不熟悉,问题就有可能出现了,因为同一个文件并不允许多个线程同时写入,否则会提示"文件正在由另一进程使用,因此该进程无法访问此文件". 这是文件的并发写入问题,就需要用到线程同步.而微软也给线程同步提供了一些相关的类可以达到这样的目的,本文使用到的 System.Thr

  • C#使用读写锁解决多线程并发问题

    一.简介 在开发程序的过程中,难免少不了写入错误日志这个关键功能.实现这个功能,可以选择使用第三方日志插件,也可以选择使用数据库,还可以自己写个简单的方法把错误信息记录到日志文件.现在我们来讲下最后一种方法: 在选择最后一种方法实现的时候,若对文件操作与线程同步不熟悉,问题就有可能出现了,因为同一个文件并不允许多个线程同时写入,否则会提示“文件正在由另一进程使用,因此该进程无法访问此文件”.这是文件的并发写入问题,就需要用到线程同步.而微软也给线程同步提供了一些相关的类可以达到这样的目的,本文使

  • Java中读写锁ReadWriteLock的原理与应用详解

    目录 什么是读写锁? 为什么需要读写锁? 读写锁的特点 读写锁的使用场景 读写锁的主要成员和结构图 读写锁的实现原理 读写锁总结 Java并发编程提供了读写锁,主要用于读多写少的场景,今天我就重点来讲解读写锁的底层实现原理 什么是读写锁? 读写锁并不是JAVA所特有的读写锁(Readers-Writer Lock)顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的. 所谓的读

  • Java并发编程之重入锁与读写锁

    重入锁 重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁.重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁阻塞,该特性的实现需要解决以下两个问题. 1.线程再次获取锁.锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取. 2.锁的最终释放.线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁.锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放

  • GO语言并发编程之互斥锁、读写锁详解

    在本节,我们对Go语言所提供的与锁有关的API进行说明.这包括了互斥锁和读写锁.我们在第6章描述过互斥锁,但却没有提到过读写锁.这两种锁对于传统的并发程序来说都是非常常用和重要的. 一.互斥锁 互斥锁是传统的并发程序对共享资源进行访问控制的主要手段.它由标准库代码包sync中的Mutex结构体类型代表.sync.Mutex类型(确切地说,是*sync.Mutex类型)只有两个公开方法--Lock和Unlock.顾名思义,前者被用于锁定当前的互斥量,而后者则被用来对当前的互斥量进行解锁. 类型sy

  • Java多线程编程中线程锁与读写锁的使用示例

    线程锁Lock Lock  相当于 当前对象的 Synchronized import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* * Lock lock = new ReentrantLock(); * lock.lock(); lock.unLock(); * 类似于 synchronized,但不能与synchronized 混用 */ public class L

  • 举例说明Java多线程编程中读写锁的使用

    以下示例为 java api并发库中 ReentrantReadWriteLock自带的实例,下面进行解读 class CachedData { Object data; volatile boolean cacheValid; ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock();//@1 if (!cacheValid) { //

  • Golang并发操作中常见的读写锁详析

    互斥锁简单粗暴,谁拿到谁操作.今天给大家介绍一下读写锁,读写锁比互斥锁略微复杂一些,不过我相信我们今天能够把他拿下! golang读写锁,其特征在于 读锁:可以同时进行多个协程读操作,不允许写操作 写锁:只允许同时有一个协程进行写操作,不允许其他写操作和读操作 读写锁有两种模式.没错!一种是读模式,一种是写模式.当他为写模式的话,作用和互斥锁差不多,只允许有一个协程抢到这把锁,其他协程乖乖排队.但是读模式就不一样了,他允许你多个协程读,但是不能写.总结起来就是: 仅读模式: 多协程可读不可写 仅

随机推荐