当前位置: 首页 > news >正文

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的使用语法:

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

 

相关文章:

  • Kafka 开启SASL/SCRAM认证 及 ACL授权(二)ACL
  • 网络基础(2)
  • 创邻科技Galaxybase—激活数据要素的核心引擎
  • JDBC批量插入数据
  • 基于SSM的摄影约拍系统
  • 竞赛选题 深度学习YOLOv5车辆颜色识别检测 - python opencv
  • C语言达到什么水平才能从事单片机工作
  • 架构实战关键知识点
  • Macos数字音乐库:Elsten Software Bliss for Mac
  • Go 匿名函数与闭包
  • ES知识点全面整理
  • a-table 动态列宽拖拽 vue
  • 关于网络协议的若干问题(一)
  • JavaEE-http/https/Tomcat(下)
  • HTTP/2 中的漏洞
  • element ui el-table表格复选框,弹框关闭取消打勾选择
  • centos清理日志和缓存
  • grafana api创建dashboard 记录
  • 【unity】【VR】白马VR课堂系列-VR开发核心基础04-主体设置-XR Rig的引入和设置
  • 从硬件结构到软件
  • 商务部:中方愿同各国一道加强合作,促进跨境电商健康可持续发展
  • 安徽六安原市长潘东旭,已任省市场监督管理局党组书记、局长
  • 金融政策支持稳市场稳预期发布会即将召开,潘功胜、李云泽、吴清将出席
  • 美妙的下午
  • 上音校园春日花艺引路人打卡,阳台音乐会吹响《玫瑰人生》
  • 遭反特朗普情绪拖累?澳大利亚联盟党大选落败、党魁痛失议席