多线程的共享资源访问之——读写锁

每天都觉得压力重重,为了释放压力,只有多学习一点点,今天的目标是比昨天的我强大一点点!那就深入理解下读写锁吧,我的代码中用的较少。

互斥锁:lock

定义:对共享资源进行互斥,仅允许一个线程进入。
在多线程编程方面遇到的第一个问题是,并发对共享资源的访问权限。当两个或多个线程共享对某个对象的访问权限且可能同时尝试修改此对象时,就会出现这个问题。当 C# 首次发布时,lock 语句实现了一种基本方法,可确保只有一个线程能访问指定资源(如数据文件),且效果很好。C# 中的 lock 关键字很容易理解,它颠覆了我们对这个问题的思考方式。

不过,简单的 lock 存在一个主要缺陷:它不区分只读访问权限和写入访问权限。代码示例如下:

Object thisLock = new Object();
lock (thisLock)
{
	//互斥访问操作    
}

读写锁:readwritelock

最简单的定义:一个支持一个写和多个读的锁。
本质上读写锁是一种特殊的自旋锁,读时阻塞写,写时阻塞读/写,与 lock 语句不同,此类可便于指定代码是将内容写入对象,还是只从对象读取内容。这样一来,多个读取器可以同时进入,但在其他所有读写线程均已完成自己的工作前,拒绝任何写入代码访问。

现在的问题是:如果使用 ReaderWriterLock 类,语法就会变得很麻烦,大量的重复代码既降低了可读性,又随时间变化增加了维护复杂性,并且代码中通常会分散有多个 try 和 finally 块。即使是简单的拼写错误,也可能会带来日后有时极难发现的灾难性影响。

通过将 ReaderWriterLockSlim 封装到简单的类中,这个问题瞬间解决,不仅重复代码不再会出现,而且还降低了小拼写错误毁一天劳动成果的风险,最重要的是,它在很大程度上有助于实现避免重复代码原则 (DRY)。看看下面的例子:

public class Synchronizer<TImpl, TIRead, TIWrite> where TImpl : TIWrite, TIRead
{
  ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
  TImpl _shared;

  public Synchronizer(TImpl shared)
  {
  //共享资源
    _shared = shared;
  }

 //读
  public void Read(Action<TIRead> functor)
  {
    _lock.EnterReadLock();
    try {
      functor(_shared);
    } finally {
      _lock.ExitReadLock();
    }
  }

  //写
  public void Write(Action<TIWrite> functor)
  {
    _lock.EnterWriteLock();
    try {
      functor(_shared);
    } finally {
      _lock.ExitWriteLock();
    }
  }
}

与互斥锁相比,读-写锁允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程),读-写锁利用了这一点。从理论上讲,与互斥锁相比,使用读-写锁所允许的并发性增强将带来更大的性能提高。但在实践中,只有在多处理器上频繁地访问读取数据结构,才能提高性能。 而在其他情况下,读-写锁的性能却比独占锁的性能要差一点,这是因为读-写锁的复杂性更高。所以要对程序进行分析,判断读-写锁是否能提高性能。

小结

读写锁的场景应该会很多,很多场景下写的频率比较低,读的频率比较高,就比较适合选用读写锁。

发布了112 篇原创文章 · 获赞 16 · 访问量 25万+

猜你喜欢

转载自blog.csdn.net/webmote/article/details/103580722
今日推荐