深入多线程之:Reader与Write Locks(读写锁)的使用详解

 更新时间:2020年6月25日 11:41  点击:1989

线程安全的一个很经常的需求是允许并发读,但是不允许并发写,例如对于文件就是这样的。

ReaderWriterLockSlim 在.net framework 3.5的时候就提供了,它是用来代替以前的”fat”版本的”ReaderWriterLock”

这两个类,有两种基本的锁----一个读锁,一个写锁。

写锁是一个完全排他锁。

读锁可以和其他的读锁兼容


因此当一个线程持有写锁的是很,所有的尝试获取读锁和写锁的线程全部阻塞,但是如果没有一个线程持有写锁,那么可以有一系列的线程并发的获取读锁。

ReaderWriterLockSlim 定义了下面几个方法来获取和释放 读写锁。

    Public void EnterReadLock();
    Public void ExitReadLock();
    Public void EnterWriteLock();
    Public void ExitWriteLock();

和Monitor.TryEnter类似,ReaderWriterLockSlim 再对应的”EnterXXX”方法上也提供了相应的”Try”版本。ReaderWriterLock提供了AcquireXXX 和 ReleaseXXX 方法,当超时发生了,ReaderWriterLock 抛出一个ApplicationException,而不是返回false。

复制代码 代码如下:

static readonly ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
        static List<int> _items = new List<int>();
        static Random _rand = new Random();

        public static void Main()
        {
            ///三个读线程
            new Thread(Read).Start();
            new Thread(Read).Start();
            new Thread(Read).Start();

            //两个写线程
            new Thread(Write).Start("A");
            new Thread(Write).Start("B");
        }

        static void Read()
        {
            while (true)
            {
                _rw.EnterReadLock();//获取读锁
                //模拟读的过程
                foreach (int i in _items)
                    Thread.Sleep(100);
                _rw.ExitReadLock();//释放读锁
            }
        }

        static void Write(object threadID)
        {
            while (true)
            {
                Console.WriteLine(_rw.CurrentReadCount + " concurrent readers");

                int newNumber = GetRandomNum(100);

                _rw.EnterWriteLock(); //获取写锁
                _items.Add(newNumber); //写数据
                _rw.ExitWriteLock();  //释放写锁
                Console.WriteLine("Thread " + threadID + " added " + newNumber);

                Thread.Sleep(100);
            }
        }

        //获取随机数
        static int GetRandomNum(int max) { lock (_rand) return _rand.Next(max); }


再实际的发布版本中,最好使用try/finally 来确保即使异常抛出了,锁也被正确的释放了。

CurrentReadCount 属性,ReaderWriterLockSlim 提供了以下属性用来监视锁。

可更新锁:

再一个原子操作里将读锁升级为写锁是很有用的,例如,假设你想要再一个list 里面写一些不存在的项的时候, 你可能会执行下面的一些步骤:

    获取一个读锁。
    测试,如果要写的东西在列表中,那么释放锁,然后返回。
    释放读锁。
    获取一个写锁
    添加项,写东西,
    释放写锁。

问题是:在第三步和第四步之间,可能有另一个线程修改了列表。

ReaderWriterLockSlim 通过一个叫做可更新锁( upgradeable lock),来解决这个问题。

一个可更新锁除了它可以在一个原子操作中变成写锁外很像一个读锁,你可以这样使用它:

    调用EnterUpgradeableReadLock 获取可更新锁。执行一些读操作,例如判断要写的东西在不在List中。调用EnterWriteLock , 这个方法会将可更新锁 升级为 写锁。执行写操作,调用ExitWriteLock 方法,这个方法将写锁转换回可更新锁。继续执行一些读操作,或什么都不做。
    调用ExitUpgradeableReadLock 释放可更新锁。

从调用者的角度来看,它很像一个嵌套/递归锁,从功能上讲,在第三步,

ReaderWriterLockSlim 在一个原子操作里面释放读锁,然后获取写锁。

可更新锁和读锁的重要区别是:尽管可更新锁可以和读锁共存,但是一次只能有一个可更新锁被获取。这样的主要目的是防止死锁。

这样我们可以修改Write方法,让它可以添加一些不在列表中的Item

复制代码 代码如下:

static void Write(object threadID)
        {
            while (true)
            {
                Console.WriteLine(_rw.CurrentReadCount + " concurrent readers");

                int newNumber = GetRandomNum(100);

                _rw.EnterUpgradeableReadLock(); //获取可更新锁
                if (!_items.Contains(newNumber)) //如果要写的东西不在列表中
                {
                    _rw.EnterWriteLock(); //可更新锁变成写锁
                    _items.Add(newNumber); //写东西
                    _rw.ExitWriteLock(); //重新变回可更新锁
                    Console.WriteLine("Thread " + threadID + " added " + newNumber); //读数据
                }
                _rw.ExitUpgradeableReadLock(); //退出可更新锁

                Thread.Sleep(100);
            }
        }


从上面的例子可以看到C#提供的读写锁功能强大,使用方便,

所以在自己编写读写锁的时候,要考虑下是否需要支持可更新锁,是否有必要自己写一个读写锁.

[!--infotagslink--]

相关文章

  • C# WinForm多线程解决界面卡死问题的完美解决方案,使用BeginInvoke

    问题描述:当我们的界面需要在程序运行中不断更新数据时,当一个textbox的数据需要变化时,为了让程序执行中不出现界面卡死的现像,最好的方法就是多线程来解决一个主线程来创建界...2020-06-24
  • c# 多线程处理多个数据的方法

    这篇文章主要介绍了c# 多线程处理多个数据的方法,帮助大家更好的理解和学习使用c#,感兴趣的朋友可以了解下...2021-03-31
  • C#基于委托实现多线程之间操作的方法

    这篇文章主要介绍了C#基于委托实现多线程之间操作的方法,实例分析了C#的委托机制与多线程交互操作的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C#多线程中的异常处理操作示例

    这篇文章主要介绍了C#多线程中的异常处理操作,涉及C#多线程及异常的捕获、处理等相关操作技巧,需要的朋友可以参考下...2020-06-25
  • 深入分析C#中的异步和多线程

    这篇文章主要介绍了C#中异步和多线程的相关资料,帮助大家更好的理解和学习c#,感兴趣的朋友可以了解下...2021-01-16
  • C#多线程与异步的区别详解

    多线程和异步操作两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性。甚至有些时候我们就认为多线程和异步操作是等同的概念。但是,多线程和异步操作还是有一些区别的。而这些区别造成了使用多线程和异步操作的时机的区别...2020-06-25
  • C#多线程之Thread类详解

    这篇文章主要为大家详细介绍了C#多线程之Thread类,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25
  • java中多线程与线程池的基本使用方法

    在Java中,我们可以利用多线程来最大化地压榨CPU多核计算的能力,下面这篇文章主要给大家介绍了关于java中多线程与线程池基本使用的相关资料,需要的朋友可以参考下...2021-09-13
  • C#中的多线程多参数传递详解

    第一种解决方案的原理是:将线程执行的方法和参数都封装到一个类里面。通过实例化该类,方法就可以调用属性来实现间接的类型安全地传递多个参数...2020-06-25
  • java多线程中执行多个程序的实例分析

    在本篇文章里小编给大家整理的是一篇关于java多线程中执行多个程序的实例分析内容,有需要的朋友们可以学习参考下。...2021-02-07
  • 解析C#多线程编程中异步多线程的实现及线程池的使用

    这篇文章主要介绍了C#多线程编程中异步多线程的实现及线程池的使用,同时对多线程的一般概念及C#中的线程同步并发编程作了讲解,需要的朋友可以参考下...2020-06-25
  • Springboot实现多线程注入bean的工具类操作

    这篇文章主要介绍了Springboot实现多线程注入bean的工具类操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-08-27
  • c# winform 关闭窗体时同时结束线程实现思路

    th.IsBackground = true解决线程问题,意思就是把线程设置为后台线程,感兴趣的朋友可以多了解下,如何有什么妙招还请多多指导哈...2020-06-25
  • C#多线程编程中的锁系统(三)

    这篇文章主要介绍了C#多线程编程中的锁系统(三),本本文主要说下基于内核模式构造的线程同步方式、事件、信号量以及WaitHandle、AutoResetEvent、ManualResetEvent等内容,需要的朋友可以参考下...2020-06-25
  • HTML5 FileReader对象的具体使用方法

    这篇文章主要介绍了HTML5 FileReader对象的具体使用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-30
  • Java多线程实现简易微信发红包的方法实例

    这篇文章主要给大家介绍了关于Java多线程实现简易微信发红包的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-01
  • c#使用多线程的几种方式示例详解

    这篇文章主要介绍了c#使用多线程的几种方式,通过示例学习c#的多线程使用方式,大家参考使用吧...2020-06-25
  • C#多线程编程中的锁系统(四):自旋锁

    这篇文章主要介绍了C#多线程编程中的锁系统(四):自旋锁,本文讲解了基础知识、自旋锁示例、SpinLock等内容,需要的朋友可以参考下...2020-06-25
  • C# StreamReader类实现读取文件的方法

    这篇文章主要介绍了C# StreamReader类实现读取文件的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-01-22
  • C#多线程及同步示例简析

    这篇文章主要为大家详细介绍了C#多线程及同步示例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25