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

 

原文地址:https://blog.csdn.net/lwf3115841/article/details/133812813

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


项目中经常遇到CSV文件的读写需求,其中的难点主要是CSV文件的解析。本文会介绍CsvHelper、TextFieldParser、正则表达式三种解析CSV文件的方法,顺带也会介绍一下CSV文件的写方法。 CSV文件标准 在介绍CSV文件的读写方法前,我们需要了解一下CSV文件的格式。 文件示例 一
简介 本文的初衷是希望帮助那些有其它平台视觉算法开发经验的人能快速转入Halcon平台下,通过文中的示例开发者能快速了解一个Halcon项目开发的基本步骤,让开发者能把精力完全集中到算法的开发上面。 首先,你需要安装Halcon,HALCON 18.11.0.1的安装包会放在文章末尾。安装包分开发和
这篇文章主要简单记录一下C#项目的dll文件管理方法,以便后期使用。 设置dll路径 参考C#开发奇技淫巧三:把dll放在不同的目录让你的程序更整洁中间的 方法一:配置App.config文件的privatePath : &lt;runtime&gt; &lt;assemblyBinding xml
在C#中的使用JSON序列化及反序列化时,推荐使用Json.NET——NET的流行高性能JSON框架,当然也可以使用.NET自带的 System.Text.Json(.NET5)、DataContractJsonSerializer、JavaScriptSerializer(不推荐)。
事件总线是对发布-订阅模式的一种实现,是一种集中式事件处理机制,允许不同的组件之间进行彼此通信而又不需要相互依赖,达到一种解耦的目的。&#xA;EventBus维护一个事件的字典,发布者、订阅者在事件总线中获取事件实例并执行发布、订阅操作,事件实例负责维护、执行事件处理程序。
通用翻译API的HTTPS 地址为https://fanyi-api.baidu.com/api/trans/vip/translate,使用方法参考通用翻译API接入文档 。&#xA;请求方式可使用 GET 或 POST 方式(Content-Type 请指定为:application/x-www-for
词云”由美国西北大学新闻学副教授、新媒体专业主任里奇·戈登(Rich Gordon)于2006年最先使用,是通过形成“关键词云层”或“关键词渲染”,对文本中出现频率较高的“关键词”的视觉上的突出。词云图过滤掉大量的文本信息,使浏览者只要一眼扫过文本就可以领略文本的主旨。&#xA;网上大部分文章介绍的是使用P
微软在.NET中对串口通讯进行了封装,我们可以在.net2.0及以上版本开发时直接使用SerialPort类对串口进行读写操作。&#xA;为操作方便,本文对SerialPort类做了一些封装,暂时取名为**SerialPortClient**。
简介 管道为进程间通信提供了平台, 管道分为两种类型:匿名管道、命名管道,具体内容参考.NET 中的管道操作。简单来说,匿名管道只能用于本机的父子进程或线程之间,命名管道可用于远程主机或本地的任意两个进程,本文主要介绍命名管道的用法。 匿名管道在本地计算机上提供进程间通信。 与命名管道相比,虽然匿名
目录自定义日志类NLog版本的日志类Serilog版本的日志类 上个月换工作,新项目又要重新搭建基础框架,把日志实现部分单独记录下来方便以后参考。 自定义日志类 代码大部分使用ChatGPT生成,人工进行了测试和优化,主要特点: 线程安全,日志异步写入文件不影响业务逻辑 支持过期文件自动清理,也可自
[TOC] # 原理简介 本文参考[C#/WPF/WinForm/程序实现软件开机自动启动的两种常用方法](https://blog.csdn.net/weixin_42288432/article/details/120059296),将里面中的第一种方法做了封装成**AutoStart**类,使
简介 FTP是FileTransferProtocol(文件传输协议)的英文简称,而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时,它也是一个应用程序(Application)。基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件。 FTP
使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。&#xA;在 C# 中,通过用方括号 ([]) 将特性名称括起来,并置于应用该特性的实体的声明上方以指定特性。
# 简介 主流的识别库主要有ZXing.NET和ZBar,OpenCV 4.0后加入了QR码检测和解码功能。本文使用的是ZBar,同等条件下ZBar识别率更高,图片和部分代码参考[在C#中使用ZBar识别条形码](https://www.cnblogs.com/w2206/p/7755656.htm
C#中Description特性主要用于枚举和属性,方法比较简单,记录一下以便后期使用。 扩展类DescriptionExtension代码如下: using System; using System.ComponentModel; using System.Reflection; /// &lt;
本文实现一个简单的配置类,原理比较简单,适用于一些小型项目。主要实现以下功能:保存配置到json文件、从文件或实例加载配置类的属性值、数据绑定到界面控件。&#xA;一般情况下,项目都会提供配置的设置界面,很少手动更改配置文件,所以选择以json文件保存配置数据。
前几天用SerialPort类写一个串口的测试程序,关闭串口的时候会让界面卡死。网上大多数方法都是定义2个bool类型的标记Listening和Closing,关闭串口和接受数据前先判断一下。我的方法是DataReceived事件处理程序用this.BeginInvoke()更新界面,不等待UI线程
约束告知编译器类型参数必须具备的功能。 在没有任何约束的情况下,类型参数可以是任何类型。 编译器只能假定 System.Object 的成员,它是任何 .NET 类型的最终基类。 如果客户端代码使用不满足约束的类型,编译器将发出错误。 通过使用 where 上下文关键字指定约束。&#xA;最常用的泛型约束为
protobuf-net是用于.NET代码的基于契约的序列化程序,它以Google设计的“protocol buffers”序列化格式写入数据,适用于大多数编写标准类型并可以使用属性的.NET语言。&#xA;protobuf-net可通过NuGet安装程序包,也可直接访问github下载源码:https:/
工作中经常遇到需要实现TCP客户端或服务端的时候,如果每次都自己写会很麻烦且无聊,使用SuperSocket库又太大了。这时候就可以使用SimpleTCP了,当然仅限于C#语言。&#xA;SimpleTCP是一个简单且非常有用的 .NET 库,用于处理启动和使用 TCP 套接字(客户端和服务器)的重复性任务