C# 中的锁
临界区与 lock 关键字
核心作用:
通过将多线程访问串行化,保护共享资源或代码段。lock 关键字是 Monitor 类的语法糖,提供异常安全的临界区实现。
实现示例:
private static readonly object _lockObj = new object(); public void ThreadSafeMethod() { lock (_lockObj) { // 临界区代码(每次仅一个线程可进入) } }
关键特性:
用户态锁(无内核切换开销)
自动调用Monitor.Enter和Monitor.Exit
必须使用专用私有对象作为锁标识
注意事项:
❌ 避免锁定this、Type实例或字符串(易引发死锁)
❌ 避免嵌套锁(需严格按顺序释放)
✅ 推荐readonly修饰锁对象
互斥锁(Mutex)
核心作用:
系统级内核锁,支持跨进程同步,但性能开销较高(用户态/内核态切换)。
实现示例:
using var mutex = new Mutex(false, "Global\\MyAppMutex"); try { // 等待锁(最大等待时间500ms) if (mutex.WaitOne(500)) { // 临界区代码 } } finally { if (mutex != null) { mutex.ReleaseMutex(); } }
关键特性:
支持跨应用程序域同步
线程终止时自动释放锁
支持命名互斥体(系统全局可见)
适用场景:
单实例应用程序控制
进程间共享文件访问
硬件设备独占访问
信号量(Semaphore)
核心作用:
通过许可计数器控制并发线程数,SemaphoreSlim为轻量级版本(用户态实现)。
实现对比:
类型 跨进程 性能 最大许可数 Semaphore ✔️ 低 系统限制 SemaphoreSlim ❌ 高 Int32.Max 代码示例:
// 创建初始3许可、最大5许可的信号量 var semaphore = new SemaphoreSlim(3, 5); semaphore.Wait(); // 获取许可 try { // 资源访问代码 } finally { semaphore.Release(); }
典型应用:
数据库连接池(限制最大连接数)
API 请求限流
批量任务并发控制
事件
用来通知线程有一些事件已发生,从而启动后继任务的开始。
读写锁
这种锁允许在其他程序正在写的情况下读取资源,所以如果资源允许脏读,用这个比较合适。
原子锁 / 自旋锁
通过原子操作 Interlocked.CompareExchange 实现“无锁”竞争。自旋锁在等待锁时会持续占用CPU资源,因此它们最适合于预期锁保持时间非常短的情况。对于长时间运行的任务,使用更传统的锁机制(如 lock 关键字)可能会更加合适。
原子性操作
Interlocked.Increment 这是一种特例,野外原子性操作本身天生线程安全,所以无需加锁。
可能相关的内容