读写锁的概念很简单,允许多个线程同时获取读锁,但同一时间只允许一个线程获得写锁,因此也称作共享-独占锁。在C#中,推荐使用ReaderWriterLockSlim类来完成读写锁的功能。

某些场合下,对一个对象的读取次数远远大于修改次数,如果只是简单的用lock方式加锁,则会影响读取的效率。而如果采用读写锁,则多个线程可以同时读取该对象,只有等到对象被写入锁占用的时候,才会阻塞。

简单的说,当某个线程进入读取模式时,此时其他线程依然能进入读取模式,假设此时一个线程要进入写入模式,那么他不得不被阻塞。直到读取模式退出为止。

同样的,如果某个线程进入了写入模式,那么其他线程无论是要写入还是读取,都是会被阻塞的。

进入写入/读取模式有2种方法:

EnterReadLock尝试进入写入模式锁定状态。

TryEnterReadLock(Int32) 尝试进入读取模式锁定状态,可以选择整数超时时间。

EnterWriteLock 尝试进入写入模式锁定状态。

TryEnterWriteLock(Int32) 尝试进入写入模式锁定状态,可以选择超时时间。

退出写入/读取模式有2种方法:

ExitReadLock 减少读取模式的递归计数,并在生成的计数为 0(零)时退出读取模式。

ExitWriteLock 减少写入模式的递归计数,并在生成的计数为 0(零)时退出写入模式。

下面演示一下用法:

  1. public class Program
  2. {
  3. static private ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
  4. static void Main(string[] args)
  5. {
  6. Thread t_read1 = new Thread(new ThreadStart(ReadSomething));
  7. t_read1.Start();
  8. Console.WriteLine("{0} Create Thread ID {1} , Start ReadSomething", DateTime.Now.ToString("hh:mm:ss fff"), t_read1.GetHashCode());
  9. Thread t_read2 = new Thread(new ThreadStart(ReadSomething));
  10. t_read2.Start();
  11. Console.WriteLine("{0} Create Thread ID {1} , Start ReadSomething", DateTime.Now.ToString("hh:mm:ss fff"), t_read2.GetHashCode());
  12. Thread t_write1 = new Thread(new ThreadStart(WriteSomething));
  13. t_write1.Start();
  14. Console.WriteLine("{0} Create Thread ID {1} , Start WriteSomething", DateTime.Now.ToString("hh:mm:ss fff"), t_write1.GetHashCode());
  15. }
  16. static public void ReadSomething()
  17. {
  18. Console.WriteLine("{0} Thread ID {1} Begin EnterReadLock...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  19. rwl.EnterReadLock();
  20. try
  21. {
  22. Console.WriteLine("{0} Thread ID {1} reading sth...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  23. Thread.Sleep(5000);//模拟读取信息
  24. Console.WriteLine("{0} Thread ID {1} reading end.", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  25. }
  26. finally
  27. {
  28. rwl.ExitReadLock();
  29. Console.WriteLine("{0} Thread ID {1} ExitReadLock...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  30. }
  31. }
  32. static public void WriteSomething()
  33. {
  34. Console.WriteLine("{0} Thread ID {1} Begin EnterWriteLock...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  35. rwl.EnterWriteLock();
  36. try
  37. {
  38. Console.WriteLine("{0} Thread ID {1} writing sth...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  39. Thread.Sleep(10000);//模拟写入信息
  40. Console.WriteLine("{0} Thread ID {1} writing end.", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  41. }
  42. finally
  43. {
  44. rwl.ExitWriteLock();
  45. Console.WriteLine("{0} Thread ID {1} ExitWriteLock...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  46. }
  47. }
  48. }

可以看到3号线程和4号线程能够同时进入读模式,而5号线程过了5秒钟后(即3,4号线程退出读锁后),才能进入写模式。

把上述代码修改一下,先开启2个写模式的线程,然后在开启读模式线程,代码如下:

  1. static void Main(string[] args)
  2. {
  3. Thread t_write1 = new Thread(new ThreadStart(WriteSomething));
  4. t_write1.Start();
  5. Console.WriteLine("{0} Create Thread ID {1} , Start WriteSomething", DateTime.Now.ToString("hh:mm:ss fff"), t_write1.GetHashCode());
  6. Thread t_write2 = new Thread(new ThreadStart(WriteSomething));
  7. t_write2.Start();
  8. Console.WriteLine("{0} Create Thread ID {1} , Start WriteSomething", DateTime.Now.ToString("hh:mm:ss fff"), t_write2.GetHashCode());
  9. Thread t_read1 = new Thread(new ThreadStart(ReadSomething));
  10. t_read1.Start();
  11. Console.WriteLine("{0} Create Thread ID {1} , Start ReadSomething", DateTime.Now.ToString("hh:mm:ss fff"), t_read1.GetHashCode());
  12. Thread t_read2 = new Thread(new ThreadStart(ReadSomething));
  13. t_read2.Start();
  14. Console.WriteLine("{0} Create Thread ID {1} , Start ReadSomething", DateTime.Now.ToString("hh:mm:ss fff"), t_read2.GetHashCode());
  15. }

结果如下:

可以看到,3号线程和4号线程都要进入写模式,但是3号线程先占用写入锁,因此4号线程不得不等了10s后才进入。5号线程和6号线程需要占用读取锁,因此等4号线程退出写入锁后才能继续下去。

TryEnterReadLock和TryEnterWriteLock可以设置一个超时时间,运行到这句话的时候,线程会阻塞在此,如果此时能占用锁,那么返回true,如果到超时时间还未占用锁,那么返回false,放弃锁的占用,直接继续执行下面的代码。

EnterUpgradeableReadLock

ReaderWriterLockSlim类提供了可升级读模式,这种方式和读模式的区别在于它还有通过调用 EnterWriteLock 或 TryEnterWriteLock 方法升级为写入模式。 因为每次只能有一个线程处于可升级模式。进入可升级模式的线程,不会影响读取模式的线程,即当一个线程进入可升级模式,任意数量线程可以同时进入读取模式,不会阻塞。如果有多个线程已经在等待获取写入锁,那么运行EnterUpgradeableReadLock将会阻塞,直到那些线程超时或者退出写入锁。

下面代码演示了如何在可升级读模式下,升级到写入锁。

  1. static public void UpgradeableRead()
  2. {
  3. Console.WriteLine("{0} Thread ID {1} Begin EnterUpgradeableReadLock...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  4. rwl.EnterUpgradeableReadLock();
  5. try
  6. {
  7. Console.WriteLine("{0} Thread ID {1} doing sth...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  8. Console.WriteLine("{0} Thread ID {1} Begin EnterWriteLock...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  9. rwl.EnterWriteLock();
  10. try
  11. {
  12. Console.WriteLine("{0} Thread ID {1} writing sth...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  13. Thread.Sleep(10000);//模拟写入信息
  14. Console.WriteLine("{0} Thread ID {1} writing end.", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  15. }
  16. finally
  17. {
  18. rwl.ExitWriteLock();
  19. Console.WriteLine("{0} Thread ID {1} ExitWriteLock...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  20. }
  21. Thread.Sleep(10000);//模拟读取信息
  22. Console.WriteLine("{0} Thread ID {1} doing end.", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  23. }
  24. finally
  25. {
  26. rwl.ExitUpgradeableReadLock();
  27. Console.WriteLine("{0} Thread ID {1} ExitUpgradeableReadLock...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  28. }
  29. }

读写锁对于性能的影响是明显的。

下面测试代码:

  1. public class Program
  2. {
  3. static private ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
  4. static void Main(string[] args)
  5. {
  6. Stopwatch sw = new Stopwatch();
  7. sw.Start();
  8. List<Task> lstTask = new List<Task>();
  9. for (int i = 0; i < 500; i++)
  10. {
  11. if (i % 25 != 0)
  12. {
  13. var t = Task.Factory.StartNew(ReadSomething);
  14. lstTask.Add(t);
  15. }
  16. else
  17. {
  18. var t = Task.Factory.StartNew(WriteSomething);
  19. lstTask.Add(t);
  20. }
  21. }
  22. Task.WaitAll(lstTask.ToArray());
  23. sw.Stop();
  24. Console.WriteLine("使用ReaderWriterLockSlim方式,耗时:" + sw.Elapsed);
  25. sw.Restart();
  26. lstTask = new List<Task>();
  27. for (int i = 0; i < 500; i++)
  28. {
  29. if (i % 25 != 0)
  30. {
  31. var t = Task.Factory.StartNew(ReadSomething_lock);
  32. lstTask.Add(t);
  33. }
  34. else
  35. {
  36. var t = Task.Factory.StartNew(WriteSomething_lock);
  37. lstTask.Add(t);
  38. }
  39. }
  40. Task.WaitAll(lstTask.ToArray());
  41. sw.Stop();
  42. Console.WriteLine("使用lock方式,耗时:" + sw.Elapsed);
  43. }
  44. static private object _lock1 = new object();
  45. static public void ReadSomething_lock()
  46. {
  47. lock (_lock1)
  48. {
  49. //Console.WriteLine("{0} Thread ID {1} reading sth...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  50. Thread.Sleep(10);//模拟读取信息
  51. //Console.WriteLine("{0} Thread ID {1} reading end.", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  52. }
  53. }
  54. static public void WriteSomething_lock()
  55. {
  56. lock (_lock1)
  57. {
  58. //Console.WriteLine("{0} Thread ID {1} writing sth...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  59. Thread.Sleep(100);//模拟写入信息
  60. //Console.WriteLine("{0} Thread ID {1} writing end.", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  61. }
  62. }
  63. static public void ReadSomething()
  64. {
  65. rwl.EnterReadLock();
  66. try
  67. {
  68. //Console.WriteLine("{0} Thread ID {1} reading sth...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  69. Thread.Sleep(10);//模拟读取信息
  70. //Console.WriteLine("{0} Thread ID {1} reading end.", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  71. }
  72. finally
  73. {
  74. rwl.ExitReadLock();
  75. }
  76. }
  77. static public void WriteSomething()
  78. {
  79. rwl.EnterWriteLock();
  80. try
  81. {
  82. //Console.WriteLine("{0} Thread ID {1} writing sth...", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  83. Thread.Sleep(100);//模拟写入信息
  84. //Console.WriteLine("{0} Thread ID {1} writing end.", DateTime.Now.ToString("hh:mm:ss fff"), Thread.CurrentThread.GetHashCode());
  85. }
  86. finally
  87. {
  88. rwl.ExitWriteLock();
  89. }
  90. }
  91. }

上述代码,就500个Task,每个Task占用一个线程池线程,其中20个写入线程和480个读取线程,模拟操作。其中读取数据花10ms,写入操作花100ms,分别测试了对于lock方式和ReaderWriterLockSlim方式。可以做一个估算,对于ReaderWriterLockSlim,假设480个线程同时读取,那么消耗10ms,20个写入操作占用2000ms,因此所消耗时间2010ms,而对于普通的lock方式,由于都是独占性的,因此480个读取操作占时间4800ms+20个写入操作2000ms=6800ms。运行结果显示了性能提升明显。

C#读写锁ReaderWriterLockSlim的使用的更多相关文章

  1. 读写锁ReaderWriterLockSlim

    读写锁的概念很简单,允许多个线程同时获取读锁,但同一时间只允许一个线程获得写锁,因此也称作共享-独占锁. 某些场合下,对一个对象的读取次数远远大于修改次数,如果只是简单的用lock方式加锁,则会影响读 ...

  2. C#读写锁ReaderWriteLockSlim的使用

    C#读写锁ReaderWriterLockSlim的使用 using System; using System.Collections.Generic; using System.Linq; usin ...

  3. 让C#轻松实现读写锁分离--封装ReaderWriterLockSlim

    ReaderWriterLockSlim 类 表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问. 使用 ReaderWriterLockSlim 来保护由多个线程读取但每次只采用一 ...

  4. 让C#轻松实现读写锁分离

    ReaderWriterLockSlim 类 表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问. 使用 ReaderWriterLockSlim 来保护由多个线程读取但每次只采用一 ...

  5. 用读写锁三句代码解决多线程并发写入文件 z

    C#使用读写锁三句代码简单解决多线程并发写入文件时提示“文件正在由另一进程使用,因此该进程无法访问此文件”的问题 在开发程序的过程中,难免少不了写入错误日志这个关键功能.实现这个功能,可以选择使用第三 ...

  6. 锁的封装 读写锁、lock

    最近由于项目上面建议使用读写锁,而去除常见的lock锁.然后就按照需求封装了下锁.以简化锁的使用.但是开发C#的童鞋都知道lock关键字用起太方便了,但是lock关键字不支持超时处理.很无奈,为了实现 ...

  7. C#使用读写锁三行代码简单解决多线程并发写入文件时线程同步的问题

    (补充:初始化FileStream时使用包含文件共享属性(System.IO.FileShare)的构造函数比使用自定义线程锁更为安全和高效,更多内容可点击参阅) 在开发程序的过程中,难免少不了写入错 ...

  8. C# 防止同时调用=========使用读写锁三行代码简单解决多线程并发的问题

    http://www.jb51.net/article/99718.htm     本文主要介绍了C#使用读写锁三行代码简单解决多线程并发写入文件时提示"文件正在由另一进程使用,因此该进程无 ...

  9. C#使用读写锁解决多线程并发写入文件时线程同步的问题

    读写锁是以 ReaderWriterLockSlim 对象作为锁管理资源的,不同的 ReaderWriterLockSlim 对象中锁定同一个文件也会被视为不同的锁进行管理,这种差异可能会再次导致文件 ...

随机推荐

  1. tcp状态机

    tcp共有11种状态,其中涉及到关闭的状态有5 个.这5 个状态相互关联,相互纠缠,而且状态变化触发都是由应用触发,但是又涉及操作系统和网络,所以正确的理解TCP 在关闭时网络状态变化情况,为我们诊断 ...

  2. Hadoop入门系列一

    作者:Aitian Ma链接:https://www.zhihu.com/question/24965053/answer/102858134来源:知乎著作权归作者所有,转载请联系作者获得授权. Ha ...

  3. Python学习路程day18

    Python之路,Day18 - Django适当进阶篇 本节内容 学员管理系统练习 Django ORM操作进阶 用户认证 Django练习小项目:学员管理系统设计开发 带着项目需求学习是最有趣和效 ...

  4. 第二章 搭建Android开发环境--读书笔记

    俗话说,工欲善其事,必先利其器,对于Android驱动开发来说,首先我们要做的就是搭建Android开发环境,我们首先要配置Linux驱动的开发环境,接着还得配置开发Android应用程序以及Andr ...

  5. 图片上传安全性问题,根据ContentType (MIME) 判断其实不准确、不安全

    图片上传常用的类型判断方法有这么几种---截取扩展名.获取文件ContentType (MIME) .读取byte来判断(这个什么叫法来着?).前两种都有安全问题.容易被上传不安全的文件,如木马什么的 ...

  6. 《深入浅出Node.js》第7章 网络编程

    @by Ruth92(转载请注明出处) 第7章 网络编程 Node 只需要几行代码即可构建服务器,无需额外的容器. Node 提供了以下4个模块(适用于服务器端和客户端): net -> TCP ...

  7. DOM example

    一:DOM简介: 文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标志语言的标准编程接口.在网页上,组织页面(或文档)的对象被组织在一个树形结构中, ...

  8. javaEE-----org.springframework.dao.InvalidDataAccessApiUsageException: Write operation

    org.springframework.dao.InvalidDataAccessApiUsageException: Write operations are not allowed in read ...

  9. mvn打包时添加version和profile

    <!-- 定义profile --> <profiles> <!-- 开发环境 --> <profile> <id>dev</id&g ...

  10. Course Schedule

    There are a total of n courses you have to take, labeled from 0 to n - 1. Some courses may have prer ...