• C#中lock 和 ReaderWriterLock 的使用总结


    线程锁是多线程并发共享数据,保证一致性的工具。多线程可以同时运行多个任务但是当多个线程同时访问共享数据时,可能导致数据不同步。当有多个线程访问同一对象的加锁方法或代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但其余线程是可以访问该对象中的非加锁代码块的。以下介绍.NET(C#)中 lock 和 ReaderWriterLock 的使用。

     

    1、lock 和 ReaderWriterLock

    lock 语句获取给定对象的互斥 lock,执行语句块,然后释放 lock。 持有 lock 时,持有 lock 的线程可以再次获取并释放 lock。 阻止任何其他线程获取 lock 并等待释放 lockReaderWriterLock支持单个写线程和多个读线程的锁。.NET Framework 有两个读取器-编写器锁, ReaderWriterLockSlimReaderWriterLock 。 建议对所有新开发的项目使用 ReaderWriterLockSlim。 虽然 ReaderWriterLockSlim 类似于 ReaderWriterLock,但不同之处在于,前者简化了递归规则以及锁状态的升级和降级规则。 ReaderWriterLockSlim 避免了许多潜在的死锁情况。 另外,ReaderWriterLockSlim 的性能显著优于 ReaderWriterLockReaderWriterLock长时间持有读取器锁或写入器锁将枯竭其他线程。 为了获得最佳性能,请考虑重构应用程序以最大程度地缩短写入的持续时间。

    lock的使用语法:

    1. lock (x)
    2. {
    3. // 加锁代码
    4. }

     lock是语法糖,代码相当于:

    1. object __lockObj = x;
    2. bool __lockWasTaken = false;
    3. try
    4. {
    5. System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
    6. // 加锁代码
    7. }
    8. finally
    9. {
    10. if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
    11. }

     

    注意:无论lock锁定的是this,还是obj,只要关心多线程锁定的对象是不是为同一个对象。当同步对共享资源的线程访问时,最好使用锁定专用对象实例(例如,private readonly object balanceLock = new object();),避免对不同的共享资源使用相同的 lock 对象实例,因为这可能导致死锁或锁争用。最好避免使用this、Type 实例、字符串实例作为lock锁定的对象。

    2、lock的使用

    lock为互斥锁,lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。当任何一个线程获取到锁后,其他线程如果需要使用该临界区内代码,则必须等待前一个线程使用完毕后释放锁。

    例如,

    1. using System;
    2. using System.Threading.Tasks;
    3. namespace ConsoleApplication
    4. {
    5. class Program
    6. {
    7. static void Main()
    8. {
    9. var account = new Account(1000);
    10. var tasks = new Task[100];
    11. for (int i = 0; i < tasks.Length; i++)
    12. {
    13. tasks[i] = Task.Run(() => Update(account));
    14. }
    15. Task.WhenAll(tasks).Wait();
    16. Console.WriteLine($"Account balance : {account.GetBalance()}");
    17. Console.ReadKey();
    18. }
    19. static void Update(Account account)
    20. {
    21. decimal[] amounts = { 0, 2, -3, 6, -2, -1, 8, -5, 11, -6 };
    22. foreach (var amount in amounts)
    23. {
    24. if (amount >= 0)
    25. {
    26. account.Credit(amount);
    27. }
    28. else
    29. {
    30. account.Debit(Math.Abs(amount));
    31. }
    32. }
    33. }
    34. }
    35. public class Account
    36. {
    37. private readonly object balanceLock = new object();
    38. private decimal balance;
    39. public Account(decimal initialBalance) => balance = initialBalance;
    40. public decimal Debit(decimal amount)
    41. {
    42. if (amount < 0)
    43. {
    44. throw new ArgumentOutOfRangeException(nameof(amount), "The debit amount cannot be negative.");
    45. }
    46. decimal appliedAmount = 0;
    47. lock (balanceLock)
    48. {
    49. if (balance >= amount)
    50. {
    51. balance -= amount;
    52. appliedAmount = amount;
    53. }
    54. }
    55. return appliedAmount;
    56. }
    57. public void Credit(decimal amount)
    58. {
    59. if (amount < 0)
    60. {
    61. throw new ArgumentOutOfRangeException(nameof(amount), "The credit amount cannot be negative.");
    62. }
    63. lock (balanceLock)
    64. {
    65. balance += amount;
    66. }
    67. }
    68. public decimal GetBalance()
    69. {
    70. lock (balanceLock)
    71. {
    72. return balance;
    73. }
    74. }
    75. }
    76. }

     

    3、ReaderWriterLock的使用

    ReaderWriterLock为读写锁,ReaderWriterLock 定义支持单个写线程和多个读线程的锁。该锁主要是解决并发读的性能问题,使用该锁可以大大提高数据并发访问的性能,只有在写时,才会阻塞所有的读锁。建议对所有新开发的项目使用 ReaderWriterLockSlim。 虽然 ReaderWriterLockSlim 类似于 ReaderWriterLock,但不同之处在于,前者简化了递归规则以及锁状态的升级和降级规则。 ReaderWriterLockSlim 避免了许多潜在的死锁情况。 另外,ReaderWriterLockSlim 的性能显著优于 ReaderWriterLock

    例如,

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Threading;
    4. using System.Threading.Tasks;
    5. namespace ConsoleApplication
    6. {
    7. class Program
    8. {
    9. static ReaderWriterLock rwl = new ReaderWriterLock();
    10. // 定义ReaderWriterLock对象。
    11. static int resource = 0;
    12. const int numThreads = 8;
    13. static bool running = true;
    14. static int readerTimeouts = 0;
    15. static int writerTimeouts = 0;
    16. static int reads = 0;
    17. static int writes = 0;
    18. public static void Main()
    19. {
    20. // 启动多个线程,对共享资源进行随机读写。
    21. Thread[] t = new Thread[numThreads];
    22. for (int i = 0; i < numThreads; i++)
    23. {
    24. t[i] = new Thread(new ThreadStart(ThreadProc));
    25. t[i].Name = new String(Convert.ToChar(i + 65), 1);
    26. t[i].Start();
    27. if (i > 10)
    28. Thread.Sleep(300);
    29. }
    30. // 等待它们全部完成。
    31. running = false;
    32. for (int i = 0; i < numThreads; i++)
    33. t[i].Join();
    34. Console.WriteLine("\nread:{0} , write:{1} , reader time-out:{2}, writer time-out:{3}",
    35. reads, writes, readerTimeouts, writerTimeouts);
    36. Console.Write("Press ENTER to exit... ");
    37. Console.ReadLine();
    38. }
    39. static void ThreadProc()
    40. {
    41. Random rnd = new Random();
    42. //随机选择线程对共享资源进行读写的方式。
    43. while (running)
    44. {
    45. double action = rnd.NextDouble();
    46. if (action < .8)
    47. ReadFromResource(10);
    48. else if (action < .81)
    49. ReleaseRestore(rnd, 50);
    50. else if (action < .90)
    51. UpgradeDowngrade(rnd, 100);
    52. else
    53. WriteToResource(rnd, 100);
    54. }
    55. }
    56. // 请求和释放读取锁,并处理超时。
    57. static void ReadFromResource(int timeOut)
    58. {
    59. try
    60. {
    61. rwl.AcquireReaderLock(timeOut);
    62. try
    63. {
    64. // 这个线程从共享资源读取是安全的。
    65. Display("reads resource value :" + resource);
    66. Interlocked.Increment(ref reads);
    67. }
    68. finally
    69. {
    70. // 确保锁已释放。
    71. rwl.ReleaseReaderLock();
    72. }
    73. }
    74. catch (ApplicationException)
    75. {
    76. // 读取锁定请求超时处理
    77. Interlocked.Increment(ref readerTimeouts);
    78. }
    79. }
    80. // 请求和释放写入锁,并处理超时
    81. static void WriteToResource(Random rnd, int timeOut)
    82. {
    83. try
    84. {
    85. rwl.AcquireWriterLock(timeOut);
    86. try
    87. {
    88. // 这个线程从共享资源访问是安全的
    89. resource = rnd.Next(500);
    90. Display("writes resource value " + resource);
    91. Interlocked.Increment(ref writes);
    92. }
    93. finally
    94. {
    95. // 确保锁已释放
    96. rwl.ReleaseWriterLock();
    97. }
    98. }
    99. catch (ApplicationException)
    100. {
    101. // 写锁请求超时处理
    102. Interlocked.Increment(ref writerTimeouts);
    103. }
    104. }
    105. // 请求读取锁,将读取锁升级为写入锁,然后再次将其降级为读取锁。
    106. static void UpgradeDowngrade(Random rnd, int timeOut)
    107. {
    108. try
    109. {
    110. rwl.AcquireReaderLock(timeOut);
    111. try
    112. {
    113. // 这个线程从共享资源读取是安全的
    114. Display("reads resource value " + resource);
    115. Interlocked.Increment(ref reads);
    116. //要写资源,要么释放读锁,要么请求写锁,或升级读锁升级
    117. //读取锁将线程放入写队列中,在any后面可能正在等待写入锁的其他线程。
    118. try
    119. {
    120. LockCookie lc = rwl.UpgradeToWriterLock(timeOut);
    121. try
    122. {
    123. //对这个线程来说,从共享资源读写是安全的。
    124. resource = rnd.Next(500);
    125. Display("writes resource value " + resource);
    126. Interlocked.Increment(ref writes);
    127. }
    128. finally
    129. {
    130. // 确保锁已释放。
    131. rwl.DowngradeFromWriterLock(ref lc);
    132. }
    133. }
    134. catch (ApplicationException)
    135. {
    136. // 事件解释升级请求超时。
    137. Interlocked.Increment(ref writerTimeouts);
    138. }
    139. // 如果锁被降级,从资源中读取仍然是安全的。
    140. Display("reads resource value: " + resource);
    141. Interlocked.Increment(ref reads);
    142. }
    143. finally
    144. {
    145. // 确保锁已释放
    146. rwl.ReleaseReaderLock();
    147. }
    148. }
    149. catch (ApplicationException)
    150. {
    151. // 读取锁定请求超时处理步骤
    152. Interlocked.Increment(ref readerTimeouts);
    153. }
    154. }
    155. //释放所有锁,之后恢复锁状态。
    156. //使用序列号来确定另一个线程是否有
    157. //获得了一个写锁,因为该线程最后一次访问资源。
    158. static void ReleaseRestore(Random rnd, int timeOut)
    159. {
    160. int lastWriter;
    161. try
    162. {
    163. rwl.AcquireReaderLock(timeOut);
    164. try
    165. {
    166. //线程从共享资源中读取数据是安全的,
    167. //读取和缓存资源的值。
    168. int resourceValue = resource; // 缓存资源值。
    169. Display("reads resource value " + resourceValue);
    170. Interlocked.Increment(ref reads);
    171. // 保存当前写入器序列号。
    172. lastWriter = rwl.WriterSeqNum;
    173. // 释放锁并保存一个cookie,以便稍后可以恢复锁。
    174. LockCookie lc = rwl.ReleaseLock();
    175. // 等待一个随机的时间间隔,然后恢复之前的锁状态。
    176. Thread.Sleep(rnd.Next(250));
    177. rwl.RestoreLock(ref lc);
    178. //检查其他线程是否在这个时间间隔内获得写锁。
    179. //如果不是,则资源的缓存值仍然有效。
    180. if (rwl.AnyWritersSince(lastWriter))
    181. {
    182. resourceValue = resource;
    183. Interlocked.Increment(ref reads);
    184. Display("resource has changed: " + resourceValue);
    185. }
    186. else
    187. {
    188. Display("resource has not changed: " + resourceValue);
    189. }
    190. }
    191. finally
    192. {
    193. // 确保锁已释放。
    194. rwl.ReleaseReaderLock();
    195. }
    196. }
    197. catch (ApplicationException)
    198. {
    199. // 读取锁定请求超时处理
    200. Interlocked.Increment(ref readerTimeouts);
    201. }
    202. }
    203. static void Display(string msg)
    204. {
    205. Console.Write("Thread {0} {1}. \r", Thread.CurrentThread.Name, msg);
    206. }
    207. }
    208. }

     

  • 相关阅读:
    ubuntu22.04.3 安装postgresql 16 rc1数据库
    基于 Github 平台的 .NET 开源项目模板. 嘎嘎实用!
    【原创】EtherCAT主站IgH解析(二)-- 如何将Igh移植到Linux/Windows/RTOS等操作系统指南
    使用Visual Studio 2022实现透明按钮和标签、POPUP样式窗体的一种工业系统的UI例程
    Docker的四种网络模式
    D. Secret Santa(构造)
    Vue 3 后端错误消息处理范例
    闭包会牺牲多少性能?它是如何产生内存消耗及性能消耗的?
    OpenLayers实战,OpenLayers调用手机陀螺仪方向实现指南针效果
    【iOS】—— MVC模式
  • 原文地址:https://blog.csdn.net/lwf3115841/article/details/133812813