1、lock 和 ReaderWriterLock
lock
语句获取给定对象的互斥 lock
,执行语句块,然后释放 lock
。 持有 lock
时,持有 lock
的线程可以再次获取并释放 lock
。 阻止任何其他线程获取 lock
并等待释放 lock
。ReaderWriterLock
支持单个写线程和多个读线程的锁。.NET Framework 有两个读取器-编写器锁, ReaderWriterLockSlim
和 ReaderWriterLock
。 建议对所有新开发的项目使用 ReaderWriterLockSlim
。 虽然 ReaderWriterLockSlim
类似于 ReaderWriterLock
,但不同之处在于,前者简化了递归规则以及锁状态的升级和降级规则。 ReaderWriterLockSlim
避免了许多潜在的死锁情况。 另外,ReaderWriterLockSlim
的性能显著优于 ReaderWriterLock
。ReaderWriterLock
长时间持有读取器锁或写入器锁将枯竭其他线程。 为了获得最佳性能,请考虑重构应用程序以最大程度地缩短写入的持续时间。
lock
的使用语法:
lock (x) { // 加锁代码 }
lock
是语法糖,代码相当于:
object __lockObj = x; bool __lockWasTaken = false; try { System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken); // 加锁代码 } finally { if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj); }
注意:无论lock
锁定的是this
,还是obj
,只要关心多线程锁定的对象是不是为同一个对象。当同步对共享资源的线程访问时,最好使用锁定专用对象实例(例如,private readonly object balanceLock = new object();
),避免对不同的共享资源使用相同的 lock
对象实例,因为这可能导致死锁或锁争用。最好避免使用this、Type 实例、字符串实例作为lock锁定的对象。
2、lock的使用
lock
为互斥锁,lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。当任何一个线程获取到锁后,其他线程如果需要使用该临界区内代码,则必须等待前一个线程使用完毕后释放锁。
例如,
using System; using System.Threading.Tasks; namespace ConsoleApplication { class Program { static void Main() { var account = new Account(1000); var tasks = new Task[100]; for (int i = 0; i < tasks.Length; i++) { tasks[i] = Task.Run(() => Update(account)); } Task.WhenAll(tasks).Wait(); Console.WriteLine($"Account balance : {account.GetBalance()}"); Console.ReadKey(); } static void Update(Account account) { decimal[] amounts = { 0, 2, -3, 6, -2, -1, 8, -5, 11, -6 }; foreach (var amount in amounts) { if (amount >= 0) { account.Credit(amount); } else { account.Debit(Math.Abs(amount)); } } } } public class Account { private readonly object balanceLock = new object(); private decimal balance; public Account(decimal initialBalance) => balance = initialBalance; public decimal Debit(decimal amount) { if (amount < 0) { throw new ArgumentOutOfRangeException(nameof(amount), "The debit amount cannot be negative."); } decimal appliedAmount = 0; lock (balanceLock) { if (balance >= amount) { balance -= amount; appliedAmount = amount; } } return appliedAmount; } public void Credit(decimal amount) { if (amount < 0) { throw new ArgumentOutOfRangeException(nameof(amount), "The credit amount cannot be negative."); } lock (balanceLock) { balance += amount; } } public decimal GetBalance() { lock (balanceLock) { return balance; } } } }
3、ReaderWriterLock的使用
ReaderWriterLock
为读写锁,ReaderWriterLock
定义支持单个写线程和多个读线程的锁。该锁主要是解决并发读的性能问题,使用该锁可以大大提高数据并发访问的性能,只有在写时,才会阻塞所有的读锁。建议对所有新开发的项目使用 ReaderWriterLockSlim
。 虽然 ReaderWriterLockSlim
类似于 ReaderWriterLock
,但不同之处在于,前者简化了递归规则以及锁状态的升级和降级规则。 ReaderWriterLockSlim
避免了许多潜在的死锁情况。 另外,ReaderWriterLockSlim
的性能显著优于 ReaderWriterLock
。
例如,
using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication { class Program { static ReaderWriterLock rwl = new ReaderWriterLock(); // 定义ReaderWriterLock对象。 static int resource = 0; const int numThreads = 8; static bool running = true; static int readerTimeouts = 0; static int writerTimeouts = 0; static int reads = 0; static int writes = 0; public static void Main() { // 启动多个线程,对共享资源进行随机读写。 Thread[] t = new Thread[numThreads]; for (int i = 0; i < numThreads; i++) { t[i] = new Thread(new ThreadStart(ThreadProc)); t[i].Name = new String(Convert.ToChar(i + 65), 1); t[i].Start(); if (i > 10) Thread.Sleep(300); } // 等待它们全部完成。 running = false; for (int i = 0; i < numThreads; i++) t[i].Join(); Console.WriteLine("\nread:{0} , write:{1} , reader time-out:{2}, writer time-out:{3}", reads, writes, readerTimeouts, writerTimeouts); Console.Write("Press ENTER to exit... "); Console.ReadLine(); } static void ThreadProc() { Random rnd = new Random(); //随机选择线程对共享资源进行读写的方式。 while (running) { double action = rnd.NextDouble(); if (action < .8) ReadFromResource(10); else if (action < .81) ReleaseRestore(rnd, 50); else if (action < .90) UpgradeDowngrade(rnd, 100); else WriteToResource(rnd, 100); } } // 请求和释放读取锁,并处理超时。 static void ReadFromResource(int timeOut) { try { rwl.AcquireReaderLock(timeOut); try { // 这个线程从共享资源读取是安全的。 Display("reads resource value :" + resource); Interlocked.Increment(ref reads); } finally { // 确保锁已释放。 rwl.ReleaseReaderLock(); } } catch (ApplicationException) { // 读取锁定请求超时处理 Interlocked.Increment(ref readerTimeouts); } } // 请求和释放写入锁,并处理超时 static void WriteToResource(Random rnd, int timeOut) { try { rwl.AcquireWriterLock(timeOut); try { // 这个线程从共享资源访问是安全的 resource = rnd.Next(500); Display("writes resource value " + resource); Interlocked.Increment(ref writes); } finally { // 确保锁已释放 rwl.ReleaseWriterLock(); } } catch (ApplicationException) { // 写锁请求超时处理 Interlocked.Increment(ref writerTimeouts); } } // 请求读取锁,将读取锁升级为写入锁,然后再次将其降级为读取锁。 static void UpgradeDowngrade(Random rnd, int timeOut) { try { rwl.AcquireReaderLock(timeOut); try { // 这个线程从共享资源读取是安全的 Display("reads resource value " + resource); Interlocked.Increment(ref reads); //要写资源,要么释放读锁,要么请求写锁,或升级读锁升级 //读取锁将线程放入写队列中,在any后面可能正在等待写入锁的其他线程。 try { LockCookie lc = rwl.UpgradeToWriterLock(timeOut); try { //对这个线程来说,从共享资源读写是安全的。 resource = rnd.Next(500); Display("writes resource value " + resource); Interlocked.Increment(ref writes); } finally { // 确保锁已释放。 rwl.DowngradeFromWriterLock(ref lc); } } catch (ApplicationException) { // 事件解释升级请求超时。 Interlocked.Increment(ref writerTimeouts); } // 如果锁被降级,从资源中读取仍然是安全的。 Display("reads resource value: " + resource); Interlocked.Increment(ref reads); } finally { // 确保锁已释放 rwl.ReleaseReaderLock(); } } catch (ApplicationException) { // 读取锁定请求超时处理步骤 Interlocked.Increment(ref readerTimeouts); } } //释放所有锁,之后恢复锁状态。 //使用序列号来确定另一个线程是否有 //获得了一个写锁,因为该线程最后一次访问资源。 static void ReleaseRestore(Random rnd, int timeOut) { int lastWriter; try { rwl.AcquireReaderLock(timeOut); try { //线程从共享资源中读取数据是安全的, //读取和缓存资源的值。 int resourceValue = resource; // 缓存资源值。 Display("reads resource value " + resourceValue); Interlocked.Increment(ref reads); // 保存当前写入器序列号。 lastWriter = rwl.WriterSeqNum; // 释放锁并保存一个cookie,以便稍后可以恢复锁。 LockCookie lc = rwl.ReleaseLock(); // 等待一个随机的时间间隔,然后恢复之前的锁状态。 Thread.Sleep(rnd.Next(250)); rwl.RestoreLock(ref lc); //检查其他线程是否在这个时间间隔内获得写锁。 //如果不是,则资源的缓存值仍然有效。 if (rwl.AnyWritersSince(lastWriter)) { resourceValue = resource; Interlocked.Increment(ref reads); Display("resource has changed: " + resourceValue); } else { Display("resource has not changed: " + resourceValue); } } finally { // 确保锁已释放。 rwl.ReleaseReaderLock(); } } catch (ApplicationException) { // 读取锁定请求超时处理 Interlocked.Increment(ref readerTimeouts); } } static void Display(string msg) { Console.Write("Thread {0} {1}. \r", Thread.CurrentThread.Name, msg); } } }