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

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

  在本例中,我们先生成一个随机数。然后获取读锁并检查该数是否存在于字典的键集合中。如果不存在,将读锁更新为写锁然后将该新键加入到字典中。始终使用try/finally代码块来确保在捕获锁后一定会释放锁。

/**
EnterReadLock() 尝试进入读取模式锁定状态。
EnterUpgradeableReadLock() 尝试进入可升级模式锁定状态。
EnterWriteLock() 尝试进入写入模式锁定状态。
ExitReadLock() 减少读取模式的递归计数,并在生成的计数为 0(零)时退出读取模式。
ExitUpgradeableReadLock() 减少可升级模式的递归计数,并在生成的计数为 0(零)时退出可升级模式。
ExitWriteLock() 减少写入模式的递归计数,并在生成的计数为 0(零)时退出写入模式。
TryEnterReadLock(Int32) 尝试进入读取模式锁定状态,可以选择整数超时时间。
TryEnterReadLock(TimeSpan) 尝试进入读取模式锁定状态,可以选择超时时间。
TryEnterUpgradeableReadLock(Int32) 尝试进入可升级模式锁定状态,可以选择超时时间。
TryEnterUpgradeableReadLock(TimeSpan) 尝试进入可升级模式锁定状态,可以选择超时时间。
TryEnterWriteLock(Int32) 尝试进入写入模式锁定状态,可以选择超时时间。
TryEnterWriteLock(TimeSpan) 尝试进入写入模式锁定状态,可以选择超时时间。 */

static void Main(string[] args)
{
  // 3个线程 读取
  new Thread(Read) { IsBackground = true }.Start();
  new Thread(Read) { IsBackground = true }.Start();
  new Thread(Read) { IsBackground = true }.Start();
  // 2个线程 写入
  new Thread(() => Write("A")) { IsBackground = true }.Start();
  new Thread(() => Write("B")) { IsBackground = true }.Start();

  Thread.Sleep(TimeSpan.FromSeconds(30));

  Console.ReadKey();
}

static readonly ReaderWriterLockSlim _rw = new();
static readonly Dictionary _items = new();

static void Read()
{
  Console.WriteLine("Reading contents of a dictionary");
  while (true)
  {
    try
    {
      _rw.EnterReadLock();// 读锁 允许多线程读取数据
      for (int i = 0; i < _items.Keys.Count; i++)
      {
        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();
    }
  }
}