• 多线程-使用ReaderWriterLockSlim类(对缓存大量的读取,和少量的写入【如果读取不到,就加读锁,然后写入缓存】)


      同时多个线程来(如从字典中)读取数据,还有另外几个线程(如向该字典中)写入数据

      比如:WEB中对缓存的读写操作(一般是一边是大量用户读取【如果读取不到,就加写锁,然后写数据到缓存】)

      由于锁 ( lock 和 Monitor ) 是线程独占式访问的,所以其对性能的影响还是蛮大的,那有没有一种方式可是实现:允许多个线程同时读数据、只允许一个线程写数据呢?答案是肯定的。

      读写锁 ReaderWriterLock 、就是 支持单个写线程和多个读线程的锁。自.NET 3.5 开始 ReaderWriterLockSlim 、登上舞台,ReaderWriterLockSlim 可以看做是 ReaderWriterLock 的升级版。 由于 ReaderWriterLockSlim  默认不支持递归调用、所以在某种意义上来说更不容易造成死锁。

      本节将描述如何使用ReaderWriterLockSlim来创建一个线程安全的机制,在多线程中对,一个集合进行读写操作。ReaderWriterLockSlim代表了一个管理资源访问的锁,允许多个线程同时读取,以及独占写。

    一、先看一个示例

    class Program
    {
        static void Main(string[] args)
        {
            new Thread(Read){ IsBackground = true }.Start();
            new Thread(Read){ IsBackground = true }.Start();
            new Thread(Read){ IsBackground = true }.Start();
    
            new Thread(() => Write("Thread 1")){ IsBackground = true }.Start();
            new Thread(() => Write("Thread 2")){ IsBackground = true }.Start();
    
            Thread.Sleep(TimeSpan.FromSeconds(30));
    
            Console.ReadKey();
        }
    
        static ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
        static Dictionary<int, int> _items = new Dictionary<int, int>();
    
        static void Read()
        {
            Console.WriteLine("Reading contents of a dictionary");
            while (true)
            {
                try
                {
                    _rw.EnterReadLock();
                    foreach (var key in _items.Keys)
                    {
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                    }
                }
                finally
                {
                    _rw.ExitReadLock();
                }
            }
        }
    
        static void Write(string threadName)
        {
            while (true)
            {
                try
                {
                    int newKey = new Random().Next(250);
                    _rw.EnterUpgradeableReadLock();
                    if (!_items.ContainsKey(newKey))
                    {
                        try
                        {
                            _rw.EnterWriteLock();
                            _items[newKey] = 1;
                            Console.WriteLine("New key {0} is added to a dictionary by a {1}", newKey, threadName);
                        }
                        finally
                        {
                            _rw.ExitWriteLock();
                        }
                    }
                    Thread.Sleep(TimeSpan.FromSeconds(0.1));
                }
                finally
                {
                    _rw.ExitUpgradeableReadLock();
                }
            }
        }
    }

    二、工作原理:

      当主程序启动时,同时运行了三个线程来从字典中读取数据,还有另外两个线程向该字典中写入数据。我们使用ReaderWriterLockSlim类来实现线程安全,该类专为这样的场景而设计。

      这里使用两种锁:读锁允许多线程读取数据,写锁在被释放前会阻塞了其他线程的所,有操作。获取读锁时还有一个有意思的场景,即从集合中读取数据时,根据当前数据而决,定是否获取一个写锁并修改该集合。一旦得到写锁,会阻止阅读者读取数据,从而浪费大量的时间,因此获取写锁后集合会处于阻塞状态。为了最小化阻塞浪费的时间,可以使用 EnterUpgradeableReadLock和ExitUpgradeableReadLock方法。先获取读锁后读取数据。如果发现必须修改底层集合,只需使用EnterWriteLock方法升级锁,然后快速执行一次写操作.最后使用ExitWriteLock释放写锁

      在本例中,我们先生成一个随机数。然后获取读锁并检查该数是否存在于字典的键集合中。如果不存在,将读锁更新为写锁然后将该新键加入到字典中。始终使用tyr/finaly代码块来确保在捕获锁后一定会释放锁,这是一项好的实践。所有的线程都被创建为后台线程。

      主线程在所有后台线程完成后会等待30秒。

    三、通过Demo我们来看一下 ReaderWriterLockSlim  的用法:

    1、EnterWriteLock   进入写模式锁定状态

    2、EnterReadLock    进入读模式锁定状态

    3、EnterUpgradeableReadLock  进入可升级的读模式锁定状态

    并且三种锁定模式都有超时机制、对应 Try... 方法,退出相应的模式则使用 Exit... 方法,而且所有的方法都必须是成对出现的。

    三、备注及注意事项

    1、对于同一把锁、多个线程可同时进入 读模式。

    2、对于同一把锁、同时只允许一个线程进入 写模式。

    3、对于同一把锁、同时只允许一个线程进入 可升级的读模式。

    4、通过默认构造函数创建的读写锁是不支持递归的,若想支持递归 可通过构造 ReaderWriterLockSlim(LockRecursionPolicy) 创建实例。

    5、对于同一把锁、同一线程不可两次进入同一锁状态(开启递归后可以)

    6、对于同一把锁、即便开启了递归、也不可以在进入读模式后再次进入写模式或者可升级的读模式(在这之前必须退出读模式)。

    7、再次强调、不建议启用递归。

    8、读写锁具有线程关联性,即 两个线程间拥有的锁的状态 相互独立不受影响、并且不能相互修改其锁的状态。

    9、升级状态:在进入可升级的读模式 EnterUpgradeableReadLock  后,可在恰当时间点 通过 EnterWriteLock   进入写模式。

    10、降级状态:可升级的读模式可以降级为读模式:即 在进入可升级的读模式 EnterUpgradeableReadLock  后, 通过首先调用读取模式 EnterReadLock 方法,然后再调用 ExitUpgradeableReadLock 方法。 

  • 相关阅读:
    C# 和 java 基本数据类型
    ESB
    sql server和mysql到底有什么区别
    MySQL服务器线程池插件
    Index Merge 算法
    任职22年,PowerShell之父将从微软离职:曾因开发PowerShell被微软降级过
    Umi 4 发布啦 🎈
    VB/VBA,可能超乎你的想象
    消息推送的场景
    DPI
  • 原文地址:https://www.cnblogs.com/gougou1981/p/12370992.html
Copyright © 2020-2023  润新知