c# 并发编程系列之五:常见的几种锁及各自的使用场景-Monitor锁
锁的核心作用是用来控制并发环境下对变量和资源的有序访问,c#中常见的锁有如下几种类型:
(1) Monitor
(2) Mutex
(3) ReaderWriterLockSlim
(4) SpinLock
(5) Semaphore
下面我们就来逐个看一看这些不同类型锁的使用场景和使用方式 。
前置条件:
为了使示例更具有参考性,我们照例还是建一个 ASP.NET core Razor的网站项目,
把并发放到网站环境下去运行,这样可以模拟一个多用户的使用场景。项目目录如下:
在此项目下新建 Finish.cshtml 和 Monitor.cshtml 两个文件,其中 Monitor.cshtml 用来执行并发 ,
执行完后跳转到 Finish.cshtml 并将结果显示出来。
1 . 在 Shared 目录下 找到 _Layout.cshtml 文件 , 将 Monitor.cshtml 页面的链接放到菜单中去,
方便我们去点击,代码如下:
<ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/Monitor">Monitor</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a> </li> <li class="nav-item"> <a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a> </li> </ul>
2 . 在 Monitor.cshtml 页面添加如下的代码,其功能是在表单中放一个Submit 按钮,
我们点击后进行 POST 提交。
<div class="text-center"> <h1 class="display-4">Welcome</h1> <p>Learn about </p> <form method="post"> <button type="submit">Monitor测试</button> </form> </div>
3 . 相应的我们在 Monitor.cshtml.cs 文件中按约定定义一个 OnPost( ) 方法来响应表单的 POST 操作,代码如下:
public class MonitorModel : PageModel { private int userCount = 10000; //模拟10000个用户同时执行 Submit 操作; private int saleCount = 0; // 定义一个实例变量,模拟记录商品的销量;
public void OnGet() { } public void OnPost() { Parallel.For(0, userCount, i => SaveOrder(i)); // 使用 Parallel.For() 模拟10000个用户并发执行下单操作。 //并发循环完后跳转到完成页面,并将用户数和购买的商品数在 Finish.cshtml 页面显示出来。 string url = string.Format("/Finish?usercount={0}&salecount={1}", userCount, saleCount); Response.Redirect(url); } public void SaveOrder(int i) {
// do something saleCount = saleCount + 1; // 用户下单完成后将商品销量 +1 } }
4. 在 Finish.cshtml 页面接收 OnPost() 方法中传递过来的值然后显示出来,代码如下:
<div>共有 <span style="font-size:18px;">@HttpContext.Request.Query["usercount"]</span> 人购买</div> <br /> <br /> <div>共卖出 <span style="font-size:18px;">@HttpContext.Request.Query["salecount"]</span> 份</div>
至此,我们就完成了一个模拟10000个用户同时在网站下单并完成的操作。
5. 把项目编译之后运行网站,画面如下:
点击按钮,得到如下的结果:
可以看到,结果和我们想象的不太一样,卖出的数量不是 10000 份,只有1559份,
即使打开页面多次执行其数量也仍然远小于10000 。
6 . 现在我们用 Monitor 对象对 SaveOrder(int i) 方法中的语句加锁 ,避免出现实际销量和我们看到的数量
不一致的情况,将 SaveOrder(int i) 改造如下:
private readonly object obj = new object(); public void SaveOrder(int i) { try { Monitor.Enter(obj); saleCount = saleCount + 1; } finally { Monitor.Exit(obj); } }
编译后刷新页面,然后再次点击按钮,得到结果如下:
和我们期望的结果一致。
7 . 接下来我们在上面的基础上做一下演化:在 Monitor.cshtml.cs 中增加
一个静态变量 staticCount ,看看页面运行结果有什么变化,Monitor.cshtml.cs 中代码修改如下:
public class MonitorModel : PageModel { private int userCount = 10000; private int saleCount = 0; private static int staticCount = 0; //增加一个静态变量 public void OnGet() { } public void OnPost() { Parallel.For(0, userCount, i => SaveOrder(i)); // 增加一个数据传递 staticcount string url = string.Format("/Finish?usercount={0}&salecount={1}&staticcount={2}", userCount, saleCount, staticCount); Response.Redirect(url); } public void SaveOrder(int i) { saleCount = saleCount + 1; // 给静态变量做 + 1 操作 staticCount = staticCount + 1; } }
在 Finish.cshtml 文件中 接收静态变量的值,代码如下:
<div>共有 <span style="font-size:18px;">@HttpContext.Request.Query["usercount"]</span> 人购买</div> <br /> <br /> <div>共卖出 <span style="font-size:18px;">@HttpContext.Request.Query["salecount"]</span> 份</div> <br /> <br /> <div>静态数量 <span style="font-size:18px;">@HttpContext.Request.Query["staticcount"]</span> 份</div>
编译后运行结果如下:
然后点 Monitor 链接 重复执行 3 次得到的结果如下表:
userCount | saleCount | staticCount(static) | 静态变量与上一次的差值 | |
第1次执行 | 共有 10000 人购买 | 共卖出 5540 份 | 静态数量 5533 份 | 5533 |
第2次执行 | 共有 10000 人购买 | 共卖出 7978 份 | 静态数量 13526 份 | 7993 |
第3次执行 | 共有 10000 人购买 | 共卖出 5328 份 | 静态数量 18815 份 | 5289 |
第4次执行 | 共有 10000 人购买 | 共卖出 4206 份 | 静态数量 23091 份 | 4276 |
根据上面的结果我们可以得出如下的结论:
1. 并发产生的原因是因为有多个线程在操作同一个变量,和这个变量是实例变量还是静态变量无关。
2. 产生并发的时候实例变量因为每次都会初始化,所以其值比正常执行时候小,静态变量就不一样了,
它只初始化一次,所以会产生累加的效果,多次运行后可能比正常值大。
最后,我们可以用lock关键字对简化对 Monitor对象的使用,代码如下:
private readonly object obj = new object(); public void SaveOrder(int i) { lock(obj) { saleCount = saleCount + 1; } }
编译后运行结果如下:
和使用Monitor对象的效果是一样的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人