线程锁是多线程并发共享数据,保证一致性的工具。多线程可以同时运行多个任务但是当多个线程同时访问共享数据时,可能导致数据不同步。当有多个线程访问同一对象的加锁方法或代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但其余线程是可以访问该对象中的非加锁代码块的。本文主要介绍.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的使用语法:

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);
        }
    }
}

推荐文档