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


    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("
    Log Count:{0}.		Writed Count:{1}.	Failed 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}
    ", 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("
    Log Count:{0}.		Writed Count:{1}.	Failed 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}
    ", 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

    技术的发展日新月异,随着时间推移,无法保证本博客所有内容的正确性。如有误导,请大家见谅,欢迎评论区指正!
    我创建了一个.NET开发交流群,用于分享学习心得和讨论相关技术难题。欢迎有兴趣的小伙伴扫码入群,相互学习!

  • 相关阅读:
    【每日更新】全栈笔记
    【SQL模板】四.插入/更新 列模板TSQL
    【SQL模板】三.插入/更新 数据模板TSQL
    【SQL模板】二.创建表视图模板TSQL
    【SQL模板】一.修改/新增存储过程TSQL
    【转】Https内部机制基础知识
    【每日更新】【SQL实用大杂烩】
    expandableListview的默认箭头箭头怎样移到右边
    ExpandableListView的首次加载全部展开,并且点击Group不收缩、
    android 制作9.png图片
  • 原文地址:https://www.cnblogs.com/wml-it/p/14793662.html
Copyright © 2020-2023  润新知