1282 字
6 分钟
简单来讲讲C#锁

🔑 简单来讲讲C#锁#

✨ 前言#

今天来说说C#中的锁,锁在日常开发中还是很常用的,但是用的不得当,或者骚操作比较多那么就会导致死锁,从而导致系统崩溃。

后面我会出一系列文章,来讲讲C#里面的代码和技巧,通过不断的学习积累,以达到我的跳槽目标。

文章中有任何错误的地方都可以指出,博主也在不断的学习中~

📖 简述#

下面问问AI来简单了解下什么是锁,AI分析还是挺详细的。

🧩 通俗理解#

  • 在现实生活里,就像一把门锁。
  • 如果一个人进了房间并把门反锁,别人就得在外面等他出来。
  • 等里面的人出来并开锁后,下一个人才能进去。

👉 编程里的“锁”也是一样的: 它保证同一时刻只有一个线程能进入临界区(共享资源的代码块),从而避免混乱。


⚙️ 技术定义#

并发编程 中,**锁(Lock)**是一种同步机制,用来 控制多个线程对共享资源的并发访问

  • 没有锁时:多个线程可能同时修改同一个变量、文件、数据库记录 → 造成 数据竞争 (Race Condition)
  • 有了锁后:一个线程进入临界区时,其它线程必须等待 → 保证 数据一致性

🔒 锁的关键特性#

  1. 互斥性
    • 一次只能有一个线程持有锁。
    • 其他线程必须等待。
  2. 可见性
    • 线程释放锁前的修改,对之后获取锁的线程是可见的。
  3. 可重入性(C# 的 lock 是可重入的)
    • 同一线程可以多次进入同一把锁,而不会死锁自己。

💡 举例#

下面就举个例子讲讲什么情况下就需要用到锁。

int a = 0;
// 并行++
Parallel.For(0, 1000, _ => {
a++;
});
// 会发现 a 的值小于 1000,因为并行操作导致了线程之间的竞争。
Console.WriteLine(a);

正常情况下,a的值应该是等于1000的,但由于这里使用了Parallel.For,会导致多个线程对同一个值进行++操作,从而导致最终的结果没有1000次。

那么如何避免这种情况呢,可以使用锁去避免。

// 使用锁解决线程竞争问题
object obj = new object();
int b = 0;
Parallel.For(0, 1000, _ => {
lock (obj) {
b++;
}
});
// 现在 b 的值一定是 1000,因为锁确保了同一时间只有一个线程可以执行 b++ 操作。
Console.WriteLine(b);

这里使用了一个object类型作为锁对象,这也是常见的锁对象,不一定非得使用object类型,其他引用类型也行。

🖼️ 运行结果#

🚀 .net9 新的锁对象#

上面已经通过简单的例子了解到了什么是锁,已经怎么使用锁,那么在.net9中可以直接使用lock对象作为锁。

ℹ️ 在使用.net9创建项目时,如果使用object类型的锁,rider编辑器会提示使用Lock类型作为锁。

⏳ 老写法#

public class Lock
{
private readonly object _lock = new();
public void Foo()
{
lock (_lock)
{
Thread.Sleep(3000);
Console.WriteLine("In Foo");
}
}
}

⚡ 新写法#

public class Deadlock
{
private readonly Lock _lock = new();
public void Foo()
{
lock (_lock)
{
Thread.Sleep(3000);
Console.WriteLine("In Foo");
}
}
}

📊 比较#

特性传统的 object + lock(obj)(Monitor-based)System.Threading.Lock + lock(newLock)
锁对象类型意图性任意引用类型,不专门为锁“设计”专门的锁类型,用意明确
编译器识别/处理Monitor.Enter/Exitlock(object) 被编译器转换为 Monitor 操作如果 lock 的目标是 Lock 类型,编译器 special-case 使用 EnterScope()/Dispose() 的新方式
内部机制使用 Monitor、SyncBlocks、Thin locks 等,涉及 object header,可能有额外开销新语义可以减少某些 Monitor 的开销,scope 块式释放锁,可能在某些场景性能更优
性能在高并发且锁竞争严重的场景下性能可能成为瓶颈在同样场景下可有更好的性能(但具体提升依赖于运行情况和 contention)
可读性/安全性object,可能误用;不容易一眼看出这是锁对象Lock 类型,代码语义直接告诉你“这是用来加锁的”

📝 总结#

锁是并发编程里的“双刃剑”。

  • 用得好 👉 能保证线程安全,避免数据错乱。
  • 用不好 👉 容易掉进性能陷阱,甚至导致死锁,拖垮整个系统。

在 .NET 9 之前,我们习惯用 object 作为锁对象,但语义模糊,容易被误用。

而新的 System.Threading.Lock 专门为锁而生,让代码更直观,也在某些场景下带来性能提升。

所以:

  • 写 demo、小项目 → 用 lock(object) 依旧没问题。
  • 写业务、追求可维护性和性能 → 建议上手 .NET 9 的 Lock,让代码更优雅、更安全。

👉 学会合理使用锁,能让你的程序更加稳定,也能减少“背锅”的机会。

🔗 相关链接#

【.NET 9新增的锁对象(Lock)是怎么一回事?】 https://www.bilibili.com/video/BV1rLp2zMEua/?share_source=copy_web&vd_source=fce337a51d11a67781404c67ec0b5084

简单来讲讲C#锁
https://blog.pljzy.top/posts/简单讲讲锁/简单讲讲锁/
作者
ZyPlj
发布于
2025-09-20
许可协议
CC BY-NC-SA 4.0
相关文章 智能推荐
1
C#中进行单元测试
.NET 技术合集 单元测试前言时隔多个月,终于抽空学习了点新知识,那么这次来记录一下C#怎么进行单元测试,单元测试是做什么的。我相信大部分刚毕业的都很疑惑单元测试是干什么的?在小厂实习了6个月后,我发现每天除了写CRUD就是写CRUD,几乎用不到单元测试。写完一个功能直接上手去测,当然这只是我个人感受,仅供参考。然后当我还在抱怨测试好烦的时候,大佬跟我说为什么不用单元测试和集成测试,我这也是有苦说不出。
2
C# LINQ基础差集计算
.NET 技术合集 C# LINQ基础差集计算前言最近开发的时候遇到个需求,接口参数为表的主键集合,用接口传递过来的参数去数据库查询数据,然后返回数据库不存在的数据。举个例子集合 1,2,3,4,5数据库 1,2,3通过查询语句查询,能从数据库查询出1,2,3来,然后与参数对比发现4,5是不存在与数据库的,那么要返回出来。
3
记录一次NPOI库导出Excel遇到的小问题解决方案
杂七杂八 在工作中经常回去导入或者导出Excel,那么我在工作中用的是NPOI库,很方便。不过在用的时候难免会出现问题,在这里记录一下这次需求遇到的问题。
4
面试题-01
杂七杂八 .NET 常问面试题
5
dotnet Minimal APIs实现动态注册端点
.NET 技术合集 dotnet Minimal APIs实现动态注册端点.
随机文章 随机推荐
1
🔑 简单来讲讲C#锁
✨ 前言
📖 简述
💡 举例
🚀 .net9 新的锁对象
📝 总结
🔗 相关链接