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

    获取一个读锁。

    测试,如果要写的东西在列表中,那么释放锁,然后返回。

    释放读锁。

    获取一个写锁

    添加项,写东西,

    释放写锁。

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

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

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

    调用EnterUpgradeableReadLock 获取可更新锁。执行一些读操作,例如判断要写的东西在不在List中。调用EnterWriteLock , 这个方法会将可更新锁 升级为 写锁。执行写操作,调用ExitWriteLock 方法,这个方法将写锁转换回可更新锁。继续执行一些读操作,或什么都不做。

    调用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#提供的读写锁功能强大,使用方便,

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

(0)

相关推荐

  • Java多线程编程之读写锁ReadWriteLock用法实例

    读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可.如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁:如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁.总之,读的时候上读锁,写的时候上写锁! 三个线程读数据,三个线程写数据示例: 可以同时读,读的时候不能写,不能同时写,写的时候不能读. 读的时候上读锁,读完解锁:写的时候上写锁,写完解锁. 注意finally解锁. package com.ljq.test.th

  • asp.net 细说文件读写操作(读写锁)

    问题大部分如下: 1:写入一些内容到某个文件中,在另一个进程/线程/后续操作中要读取文件内容的时候报异常,提示 System.IO.IOException: 文件"XXX"正由另一进程使用,因此该进程无法访问此文件. 2:在对一个文件进行一些操作后(读/写),随后想追加依然报System.IO.IOException: 文件"XXX"正由另一进程使用,因此该进程无法访问此文件.次问题与1相似. 3:对一个文件进行一些操作后,想删除文件,依然报System.IO.IO

  • 如何使用C#读写锁ReaderWriterLockSlim

    读写锁的概念很简单,允许多个线程同时获取读锁,但同一时间只允许一个线程获得写锁,因此也称作共享-独占锁.在C#中,推荐使用ReaderWriterLockSlim类来完成读写锁的功能. 某些场合下,对一个对象的读取次数远远大于修改次数,如果只是简单的用lock方式加锁,则会影响读取的效率.而如果采用读写锁,则多个线程可以同时读取该对象,只有等到对象被写入锁占用的时候,才会阻塞. 简单的说,当某个线程进入读取模式时,此时其他线程依然能进入读取模式,假设此时一个线程要进入写入模式,那么他不得不被阻塞

  • Java并发编程之显示锁ReentrantLock和ReadWriteLock读写锁

    在Java5.0之前,只有synchronized(内置锁)和volatile. Java5.0后引入了显示锁ReentrantLock. ReentrantLock概况 ReentrantLock是可重入的锁,它不同于内置锁, 它在每次使用都需要显示的加锁和解锁, 而且提供了更高级的特性:公平锁, 定时锁, 有条件锁, 可轮询锁, 可中断锁. 可以有效避免死锁的活跃性问题.ReentrantLock实现了 Lock接口: 复制代码 代码如下: public interface Lock {  

  • 深入多线程之:Reader与Write Locks(读写锁)的使用详解

    线程安全的一个很经常的需求是允许并发读,但是不允许并发写,例如对于文件就是这样的. ReaderWriterLockSlim 在.net framework 3.5的时候就提供了,它是用来代替以前的"fat"版本的"ReaderWriterLock" 这两个类,有两种基本的锁----一个读锁,一个写锁. 写锁是一个完全排他锁. 读锁可以和其他的读锁兼容 因此当一个线程持有写锁的是很,所有的尝试获取读锁和写锁的线程全部阻塞,但是如果没有一个线程持有写锁,那么可以有一系

  • Rust Atomics and Locks内存序Memory Ordering详解

    目录 Rust内存序 重排序和优化 happens-before Relexed Ordering Release 和 Acquire Ordering SeqCst Ordering Rust内存序 Memory Ordering规定了多线程环境下对共享内存进行操作时的可见性和顺序性,防止了不正确的重排序(Reordering). 重排序和优化 重排序是指编译器或CPU在不改变程序语义的前提下,改变指令的执行顺序.在单线程环境下,重排序可能会带来性能提升,但在多线程环境下,重排序可能会破坏程序

  • Android编程之ICS式下拉菜单PopupWindow实现方法详解(附源码下载)

    本文实例讲述了Android编程之ICS式下拉菜单PopupWindow实现方法.分享给大家供大家参考,具体如下: 运行效果截图如下: 右边这个就是下拉菜单啦,看见有的地方叫他 ICS式下拉菜单,哎哟,不错哦! 下面先讲一下实现原理: 这种菜单实际上就是一个弹出式的菜单,于是我们想到android PopupWindow 类,给他设置一个view 在弹出来不就OK了吗. PopupWindow 的用法也很简单 主要方法: 步骤1.new 一个实例出来,我们使用这个构造方法即可, 复制代码 代码如

  • Java多线程之readwritelock读写分离的实现代码

    在多线程开发中,经常会出现一种情况,我们希望读写分离.就是对于读取这个动作来说,可以同时有多个线程同时去读取这个资源,但是对于写这个动作来说,只能同时有一个线程来操作,而且同时,当有一个写线程在操作这个资源的时候,其他的读线程是不能来操作这个资源的,这样就极大的发挥了多线程的特点,能很好的将多线程的能力发挥出来. 在Java中,ReadWriteLock这个接口就为我们实现了这个需求,通过他的实现类ReentrantReadWriteLock我们可以很简单的来实现刚才的效果,下面我们使用一个例子

  • Java多线程之Park和Unpark原理

    一.基本使用 它们是 LockSupport 类中的方法 // 暂停当前线程 LockSupport.park(); // 恢复某个线程的运行 LockSupport.unpark(暂停线程对象) 应用:先 park 再 unpark Thread t1 = new Thread(() -> { log.debug("start..."); sleep(1); log.debug("park..."); LockSupport.park(); log.debu

  • java多线程之CyclicBarrier的使用方法

    java多线程之CyclicBarrier的使用方法 public class CyclicBarrierTest { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); final CyclicBarrier cb = new CyclicBarrier(3); for(int i=0;i<3;i++){ Runnable runnable = n

  • C#多线程之Thread中Thread.Join()函数用法分析

    本文实例讲述了C#多线程之Thread中Thread.Join()函数用法.分享给大家供大家参考.具体分析如下: Thread.Join()在MSDN中的解释:Blocks the calling thread until a thread terminates 当NewThread调用Join方法的时候,MainThread就被停止执行, 直到NewThread线程执行完毕. Thread oThread = new Thread(new ThreadStart(oAlpha.Beta));

  • 深入多线程之:用Wait与Pulse模拟一些同步构造的应用详解

    你可能在上篇文章中<深入多线程之:双向信号与竞赛的用法分析>注意到了这个模式:两个Waiting 循环都要下面的构造: 复制代码 代码如下: lock(_locker){        while(!_flag) Monitor.Wait(_locker);        _flag = false;} 在这里_flag被另一线程设置为true.这是,从作用上讲,这里在模仿AutoResetEvent.如果我们将 _flag = false;去掉,那么我们就得到了一个基本的ManualRese

  • Java线程之join_动力节点Java学院整理

    join()介绍 join() 定义在Thread.java中. join() 的作用:让"主线程"等待"子线程"结束之后才能继续运行.这句话可能有点晦涩,我们还是通过例子去理解: // 主线程 public class Father extends Thread { public void run() { Son s = new Son(); s.start(); s.join(); ... } } // 子线程 public class Son extends

  • C#多线程之Thread中Thread.IsAlive属性用法分析

    本文实例讲述了C#多线程之Thread中Thread.IsAlive属性用法.分享给大家供大家参考.具体如下: Thread.IsAlive属性 ,表示该线程当前是否为可用状态 如果线程已经启动,并且当前没有任何异常的话,则是true,否则为false Start()后,线程不一定能马上启动起来,也许CPU正在忙其他的事情,但迟早是会启动起来的! Thread oThread = new Thread(new ThreadStart(Back.Start)); oThread.Start();

随机推荐