深入浅出多线程系列之十五:Reader /Write Locks (读写锁)

简介:

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

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 里面写一些不存在的项的时候, 你可能会执行下面的一些步骤:

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

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

 

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

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

  1. 调用EnterUpgradeableReadLock 获取可更新锁。
  2. 执行一些读操作,例如判断要写的东西在不在List中。
  3. 调用EnterWriteLock , 这个方法会将可更新锁 升级为 写锁。
  4. 执行写操作,
  5. 调用ExitWriteLock 方法,这个方法将写锁转换回可更新锁。
  6. 继续执行一些读操作,或什么都不做。
  7. 调用 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#提供的读写锁功能强大,使用方便,

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






本文转自LoveJenny博客园博客,原文链接:http://www.cnblogs.com/LoveJenny/archive/2011/06/07/2060968.html,如需转载请自行联系原作者
目录
相关文章
|
4月前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
50 2
|
12天前
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
|
2月前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
41 6
|
3月前
|
运维 API 计算机视觉
深度解密协程锁、信号量以及线程锁的实现原理
深度解密协程锁、信号量以及线程锁的实现原理
55 2
|
3月前
|
Java 应用服务中间件 测试技术
Java21虚拟线程:我的锁去哪儿了?
【10月更文挑战第8天】
55 0
|
3月前
|
安全 调度 数据安全/隐私保护
iOS线程锁
iOS线程锁
35 0
|
3月前
|
Java API
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
【多线程】乐观/悲观锁、重量级/轻量级锁、挂起等待/自旋锁、公平/非公锁、可重入/不可重入锁、读写锁
46 0
|
3月前
|
安全 Java 程序员
【多线程-从零开始-肆】线程安全、加锁和死锁
【多线程-从零开始-肆】线程安全、加锁和死锁
62 0
|
3月前
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
4月前
|
存储 算法 Java
关于python3的一些理解(装饰器、垃圾回收、进程线程协程、全局解释器锁等)
该文章深入探讨了Python3中的多个重要概念,包括装饰器的工作原理、垃圾回收机制、进程与线程的区别及全局解释器锁(GIL)的影响等,并提供了详细的解释与示例代码。
41 0