深入浅出多线程系列之十五: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#提供的读写锁功能强大,使用方便,

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

 

参考资料:

http://www.albahari.com/threading/

CLR Via C# 3.0

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java中,可以使用Semaphore类来实现信号量,用于控制多个线程对共享资源的访问。Semaphore可以控制同时访问共享资源的线程数量,从而实现对共享资源的安全访问。 读写锁是一种特殊的锁,用于控制对共享资源的读写操作。在读写锁中,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。在Java中,可以通过ReentrantReadWriteLock类来实现读写锁。 具体实现如下: 1. 首先定义一个ReentrantReadWriteLock对象,用于控制对共享资源的访问。 2. 在读取共享资源时,使用读锁(readLock())进行加锁,允许多个线程同时读取。 3. 在写入共享资源时,使用写锁(writeLock())进行加锁,只允许一个线程进行写操作。 示例代码如下: ``` import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockExample { private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final Object sharedObject = new Object(); public void read() { lock.readLock().lock(); try { // 读取共享资源 System.out.println("Reading shared object: " + sharedObject.toString()); } finally { lock.readLock().unlock(); } } public void write(Object object) { lock.writeLock().lock(); try { // 写入共享资源 sharedObject = object; System.out.println("Writing shared object: " + sharedObject.toString()); } finally { lock.writeLock().unlock(); } } } ``` 在上面的示例代码中,使用了ReentrantReadWriteLock类实现了读写锁。在读取共享资源时,使用读锁进行加锁,允许多个线程同时读取;在写入共享资源时,使用写锁进行加锁,只允许一个线程进行写操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值