C#中lock 和 ReaderWriterLock 的使用总结
线程锁是多线程并发共享数据,保证一致性的工具。多线程可以同时运行多个任务但是当多个线程同时访问共享数据时,可能导致数据不同步。当有多个线程访问同一对象的加锁方法或代码块时,同一时间只有一个线程在执行,其余线程必须要等待当前线程执行完之后才能执行该代码段。但其余线程是可以访问该对象中的非加锁代码块的。以下介绍.NET(C#)中 lock 和 ReaderWriterLock 的使用。
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);elseWriteToResource(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);}}
}
相关文章:
C#中lock 和 ReaderWriterLock 的使用总结
线程锁是多线程并发共享数据,保证一致性的工具。多线程可以同时运行多个任务但是当多个线程同时访问共享数据时,可能导致数据不同步。当有多个线程访问同一对象的加锁方法或代码块时,同一时间只有一个线程在执行,其余线程必须要等…...
Mac下通过nvm管理node
背景 本地有两个项目,老项目需要用到node 14,新项目需要用node 16,所以只能通过nvm来管理node了 卸载原始的node 我的node是通过官网的.pkg文件安装的,可以通过以下命令进行删除 sudo rm -rf /usr/local/{bin/{node,npm},lib/…...
易点易动固定资产管理系统:RFID出入监控,保障固定资产安全
在企业管理中,固定资产的安全和管理一直是一项重要的任务。企业往往面临着固定资产丢失、盗窃和不当使用等问题,给企业带来巨大的经济损失和管理难题。为了解决这些问题,我们推出了易点易动固定资产管理系统,结合RFID出入监控技术…...
Vue封装组件并发布到npm仓库
1. 环境准备 因为我们此次封装的是Vue组件,所以我们直接在Vue脚手架项目里面进行封装即可。 (1)初始化Vue项目 vue create lin-vue (2)运行项目 npm run serve 2. 组件封装 新建src/components文件夹 因为我们可…...
python+深度学习+opencv实现植物识别算法系统 计算机竞赛
0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 基于深度学习的植物识别算法研究与实现 🥇学长这里给一个题目综合评分(每项满分5分) 难度系数:4分工作量:4分创新点:4分 🧿 更多…...
基于springboot实现医院急诊平台系统项目【项目源码】
基于springboot实现医院急诊平台系统演示 Spring Boot框架 Spring Boot是Pivotal团队的一个新框架,旨在简化新Spring应用程序的初始设置和开发。该框架使用特定的配置方法,无需开发人员定义样板配置。通过这种方式,Spring Boot旨在成为蓬勃发…...
【02】基础知识:React - jsx语法规则
一、jsx 简介 全称为JavaScript XML,是 react 定义的一种类似于 XML 的 JS 扩展语法 JS XML 本质是 React.createElement(component, props, …children) 方法的语法糖,用来简化创建虚拟 DOM 写法:var ele <h1>Hello JSX!</h1&…...
C语言 —— 指针
目录 1. 指针是什么? 2. 指针和指针类型的关系 2.1 指针的解引用 2.2 指针-整数 3. 野指针 3.1 野指针成因 1. 指针未初始化 2. 指针越界访问 3. 指针指向的空间释放 3.2 如何规避野指针 4. 指针运算 4.1 指针-整数 4.2 指针-指针 指针-指针的使用 4.3 指针的关系运…...
淘宝店铺所有商品数据接口,淘宝整店所有商品数据接口,淘宝店铺商品接口,淘宝API接口
淘宝店铺所有商品数据接口可以通过淘宝开放平台获取。以下是具体步骤: 在开放平台注册成为开发者并创建一个应用,获取到所需的 App Key 和 App Secret 等信息。使用获取到的 App Key 和 App Secret 进行签名和认证,获取 Access Token。调用开…...
【Redis】Java客户端使用zset命令
zadd/zrange zcard zrem zscore zrank...
记录一个@Transaction注解引发的bug
记录一个Transactional(readOnly true)注解引发的bug 一、问题代码和报错 1-1 问题代码模拟 引发这个问题的三大要素分别是: 事务注解任意数据库操作数据库操作后执行耗时业务(耗时超过数据库配置的超时时间) //1.这里是问题的核心之一…...
解决docker使用pandarallel报错OSError: [Errno 28] No space left on device
参考:https://github.com/nalepae/pandarallel/issues/127 在使用pandarallel报错OSError: [Errno 28] No space left on device,根据上述issue发现确实默认使用的MEMORY_FS_ROOT为 /dev/shm,而在docker环境下这个目录大小只有64M࿰…...
Javascript自定义页面复制事件
Javascript自定义页面复制事件 – WhiteNights Site 2023年10月13日 文章访问量:90 标签:Javascript 监听copy事件以达到自定义页面复制功能的效果。 写者注 需要注意的是,浏览器的部分拓展插件(如迅雷)会导致本文…...
Nginx:反向代理(示意图+配置)
示意图: 反向代理 反向代理(Reverse Proxy)是代理服务器的一种,它代表服务器接收客户端的请求,并将这些请求转发到适当的服务器。当请求在后端服务器完成之后,反向代理搜集请求的响应并将其传输给客户端。…...
macbook笔记本电脑内存怎么清理才能干净流畅?
假如你还在为“你的系统内存不足”的提示所困扰,或者你的Mac电脑突然运行缓慢和卡顿,那么你一般需要认真了解一下macbook内存怎么清理了? MacBook是功能强大的电脑,这点毫无疑问,但是它仍旧会随着时间推移变得运行缓慢。值得庆幸…...
spark 与 mapreduce 对比
Spark 为什么比 MapReduce 快总结 首先澄清几个误区: 1)两者都是基于内存计算的,任何计算框架都肯定是基于内存的,所以说网上所说的 Spark 是基于内存计算所以快,显然是错误的。 2)DAG 计算模型减少的是磁…...
kafka 相关概念
1 kafka 生产者 kafka 用push的方式把消息推送到topic 每个topic下可以有多个分区, 可以用hash 也可以用轮询的方式指定分区 每个分区内部是可以保证顺序的,但是整体无法保证顺序,除非设置成一个topic只有一个分区。 kafka这种多分区的设置 带…...
Ubuntu下vscode配置OpenCV以及Libtorch
opencv安装 sudo apt-get updatesudo apt-get install libopencv-dev 该方式安装的版本可能比较旧。 测试代码 #include <opencv2/opencv.hpp>#include <iostream>int main() {cv::Mat image cv::imread("t.png");cv::imshow("Image", ima…...
关于共识算法Raft的常见误解
关于共识算法Raft的常见误解 Raft 共识算法最终一致性与线性一致性日志的覆盖与删除Remove节点时需要skip 总结参考文档 Raft 共识算法 最近翻了翻Raft相关的资料,同时也总结了日常工作的一些积累,就当做Raft技术笔记吧。 由于工作的关系,Ra…...
Python学习基础笔记七十——模块和库1
模块和库: 一个python代码文件就实现了功能。功能比较单一。 在企业中,项目开发的文件,可能有成百上千个。 不同的代码文件,实现了不同的功能模块,就像一块块积木一样。这些功能文件整合起来,实现一个完…...
如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡
何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践,很多人以为AI已经强大到不需要程序员了,其实不是,AI更加需要程序员,普通人…...
华为OD机考- 简单的自动曝光/平均像素
import java.util.Arrays; import java.util.Scanner;public class DemoTest4 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint[] arr Array…...
