一文带你了解.Net混合锁和lock语句

本文主要讲解.Net基于Monitor.Enter和lock实现互斥锁
Monitor.Enter实现
相比前面的锁来说,混合锁的性能更高,任何引用类型的对象都可以做为锁对象,不需要事先创建指定类型的实例,并且设计的非托管的资源由.Net运行时自动释放,不需要手动调用释放函数,获取和释放混合锁需要使用System.Threading.Monitor类中的函数。使用Monitor使用混合锁的例子如下:
using System;
using System.Threading;
namespace MixedLockDemo
{
/// <summary>
/// 混合锁Demo
/// </summary>
public static class NitiveLockDemo
{
private static readonly object _lock = new();
private static int _counterA = 0;
private static int _counterB = 0;
public static void IncrementCounters()
{
//定义变量
var lockObject = _lock;
bool lockTaken = false;
try
{
Console.WriteLine($"开始执行锁前的数值:{_counterA},{_counterB}");
// 获取锁
Monitor.Enter(_lock, ref lockTaken);
++_counterA;
++_counterB;
}
finally
{
//如果锁wei
if (!lockTaken)
{
Monitor.Exit(lockObject);
}
}
}
public static void GetCounters(ref int counterA, ref int coubterB)
{
//定义变量
var lockObject = _lock;
bool lockTaken = false;
try
{
Monitor.Enter(lockObject, ref lockTaken);
counterA = _counterA;
coubterB = _counterB;
}
finally
{
if (!lockTaken)
{
Monitor.Exit(lockObject);
}
}
}
}
}
lock实现
C# 调用lock语句来简化System.Threading.Monitor类获取和释放锁的代码。以下是使用lock的实例
namespace MixedLockDemo
{
/// <summary>
/// 封装后的lock语句使用
/// </summary>
public static class PackageingLockDemo
{
private static readonly object _lock = new();
private static int _counterA = 0;
private static int _counterB = 0;
/// <summary>
/// 增加
/// </summary>
public static void IncrementCounters()
{
lock (_lock)
{
++_counterA;
++_counterB;
}
}
/// <summary>
/// 获取
/// </summary>
/// <param name="counterA"></param>
/// <param name="coubterB"></param>
public static void GetCounters(ref int counterA, ref int coubterB)
{
lock (_lock)
{
counterA = _counterA;
coubterB = _counterB;
}
}
}
}
概念
混合锁的特征是在获取失败后像自旋锁一样重试一定的次数,超过一定次数后再安排线程进入等待状态,
混合所的好处是,如果第一次获取锁失败,但其他线程马上释放了锁,当前线程在下一轮重试可以获取成功,不需要执行毫秒级的线程调度处理;如果其他线程在短时间内没有释放锁,线程会在超过重试次数后进入等待状态,以避免消耗CPU资源,因此混合锁适用于大部分场景。
所有引用类型的对象都可以作为锁对象的原理是,引用类型的对象都有一个32位(4字节)的对象头,对象头的位置在对象地址之前,例如对象的内容在内存地址中0×7fff2008时,对象头的地址在0×7fff2004。在32位的对象头中,高6位用于储存标志,低26位储存的内容根据标志而定,可以存储当前获取该锁的线程Id和进入次数(用入实现可重入),也可以储存同步块索引。
同步块是一个包含所属线程对象,进入次数和事件对象的对象。事件对象可用于让线程进入等待状态和唤醒线程,同步块会按需要创建(如果只是用自旋锁可获取锁则无需创建)并自动释放,.Net运行时内部有一个储存同步块的数组,同步块索引指的是同步块在这个数组中的索引.
释放锁和获取锁流程图
本文基于.Net Core底层入门总结内容
如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧🙂
个人微信
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!