深入浅出多线程系列之十五: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,如需转载请自行联系原作者
目录
相关文章
|
29天前
|
安全 编译器 C#
C#学习相关系列之多线程---lock线程锁的用法
C#学习相关系列之多线程---lock线程锁的用法
|
2月前
|
安全 Java C++
解释Python中的全局解释器锁(GIL)和线程安全的概念。
解释Python中的全局解释器锁(GIL)和线程安全的概念。
25 0
|
1月前
|
存储 安全 Java
并发编程知识点(volatile、JMM、锁、CAS、阻塞队列、线程池、死锁)
并发编程知识点(volatile、JMM、锁、CAS、阻塞队列、线程池、死锁)
71 3
|
2天前
|
Java 程序员 编译器
Java中的线程同步与锁优化策略
【4月更文挑战第14天】在多线程编程中,线程同步是确保数据一致性和程序正确性的关键。Java提供了多种机制来实现线程同步,其中最常用的是synchronized关键字和Lock接口。本文将深入探讨Java中的线程同步问题,并分析如何通过锁优化策略提高程序性能。我们将首先介绍线程同步的基本概念,然后详细讨论synchronized和Lock的使用及优缺点,最后探讨一些锁优化技巧,如锁粗化、锁消除和读写锁等。
|
10天前
|
安全 Java 调度
深入理解Java中的线程安全与锁机制
【4月更文挑战第6天】 在并发编程领域,Java语言提供了强大的线程支持和同步机制来确保多线程环境下的数据一致性和线程安全性。本文将深入探讨Java中线程安全的概念、常见的线程安全问题以及如何使用不同的锁机制来解决这些问题。我们将从基本的synchronized关键字开始,到显式锁(如ReentrantLock),再到读写锁(ReadWriteLock)的讨论,并结合实例代码来展示它们在实际开发中的应用。通过本文,读者不仅能够理解线程安全的重要性,还能掌握如何有效地在Java中应用各种锁机制以保障程序的稳定运行。
|
28天前
|
Linux API C++
【Linux C/C++ 线程同步 】Linux API 读写锁的编程使用
【Linux C/C++ 线程同步 】Linux API 读写锁的编程使用
18 1
|
1月前
|
安全 Java 开发者
Java中的并发编程:探索线程安全与锁机制
【2月更文挑战第12天】 本文深入探讨Java并发编程的核心概念,特别是线程安全和锁机制。不同于传统的技术文章摘要,我们将通过一个实际案例来展开讨论,即如何在多线程环境下保证数据的一致性和完整性。我们将从基础的线程概念入手,逐步深入到synchronized关键字、显式锁(如ReentrantLock),以及其他并发工具类(如CountDownLatch、CyclicBarrier等)的应用。通过本文,读者不仅能够掌握Java并发编程的理论知识,还能了解到如何在实际开发中合理地应用这些并发机制,以提升应用程序的性能和稳定性。
15 2
|
2月前
|
并行计算 安全 Python
深入理解Python多线程:GIL全局解释器锁的影响
深入理解Python多线程:GIL全局解释器锁的影响
|
2月前
|
安全 Python
在Python中,如何使用锁和条件变量来确保代码片段是线程安全的?
在Python中,如何使用锁和条件变量来确保代码片段是线程安全的?
18 0
|
2月前
|
安全 Python
在Python中,如何正确使用锁和条件变量来确保线程安全?
在Python中,如何正确使用锁和条件变量来确保线程安全?