隐藏

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

发布:2022/12/26 14:30:00作者:管理员 来源:本站 浏览次数:486

一、简介


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


在选择最后一种方法实现的时候,若对文件操作与线程同步不熟悉,问题就有可能出现了,因为同一个文件并不允许多个线程同时写入,否则会提示“文件正在由另一进程使用,因此该进程无法访问此文件”。这是文件的并发写入问题,就需要用到线程同步。而微软也给线程同步提供了一些相关的类可以达到这样的目的,本文使用到的 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