在C#中,锁是用于多线程编程中同步访问共享资源的重要机制。以下是C#中主要的锁类型及其特点和应用场景:
1. lock
关键字 (Monitor类)
特点:
- 最常用的锁机制,实际上是语法糖,底层使用
Monitor
类 - 提供互斥访问,同一时间只允许一个线程进入临界区
- 支持重入(同一线程可以多次获取同一个锁)
- 不支持超时设置
- 基于对象引用作为同步对象
应用场景:
- 简单的线程同步需求
- 保护共享数据结构的访问
- 需要简单互斥的场景
示例:
csharp
复制
private readonly object _lockObj = new object();void ThreadSafeMethod()
{lock (_lockObj){// 临界区代码}
}
2. Monitor
类
特点:
lock
关键字的底层实现- 提供更多控制,如
TryEnter
方法可设置超时 - 支持条件等待(
Wait
/Pulse
/PulseAll
) - 需要手动释放锁
应用场景:
- 需要超时控制的同步场景
- 需要更精细控制的线程同步
- 生产者-消费者模式
示例:
csharp
复制
private readonly object _monitorObj = new object();void ThreadSafeMethod()
{if (Monitor.TryEnter(_monitorObj, TimeSpan.FromSeconds(1))){try{// 临界区代码}finally{Monitor.Exit(_monitorObj);}}
}
3. Mutex
(互斥体)
特点:
- 系统范围的锁,可用于跨进程同步
- 比
Monitor
更重量级,性能开销更大 - 支持命名,可用于跨进程同步
- 支持超时设置
- 需要手动释放
应用场景:
- 跨进程的同步需求
- 需要系统范围互斥的场景
- 长时间持有的锁
示例:
csharp
复制
using var mutex = new Mutex(false, "Global\\MyNamedMutex");try
{if (mutex.WaitOne(TimeSpan.FromSeconds(1))){// 临界区代码}
}
finally
{mutex.ReleaseMutex();
}
4. Semaphore
和 SemaphoreSlim
特点:
- 信号量,允许多个线程同时访问资源(数量可控制)
Semaphore
是系统范围的,SemaphoreSlim
是轻量级的进程内版本- 适合资源池场景
SemaphoreSlim
性能更好,但不支持跨进程
应用场景:
- 限制并发访问数量
- 资源池管理
- 需要允许多个线程同时访问但有限制的场景
示例:
csharp
复制
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3, 3); // 允许3个并发async Task AccessResourceAsync()
{await _semaphore.WaitAsync();try{// 临界区代码(最多3个线程同时执行)}finally{_semaphore.Release();}
}
5. ReaderWriterLock
和 ReaderWriterLockSlim
特点:
- 读写锁,允许多个读或单个写
ReaderWriterLockSlim
是改进版本,性能更好- 支持递归(可配置)
- 读锁可升级为写锁
应用场景:
- 读多写少的场景
- 需要区分读写访问的共享资源
- 数据库缓存等高频读取偶尔写入的场景
示例:
csharp
复制
private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();void ReadData()
{_rwLock.EnterReadLock();try{// 读取操作}finally{_rwLock.ExitReadLock();}
}void WriteData()
{_rwLock.EnterWriteLock();try{// 写入操作}finally{_rwLock.ExitWriteLock();}
}
6. SpinLock
特点:
- 自旋锁,线程会忙等待而不是阻塞
- 轻量级,适合短时间等待
- 不支持递归(默认)
- 值类型,没有GC压力
应用场景:
- 极短时间的临界区保护
- 高频率、低竞争的同步场景
- 性能关键的代码路径
示例:
csharp
复制
private SpinLock _spinLock = new SpinLock();void ThreadSafeMethod()
{bool lockTaken = false;try{_spinLock.Enter(ref lockTaken);// 临界区代码}finally{if (lockTaken)_spinLock.Exit();}
}
7. Interlocked
类
特点:
- 提供原子操作,无需锁
- 性能极高
- 仅限于简单操作(递增、递减、比较交换等)
应用场景:
- 简单的原子操作
- 计数器等简单共享变量
- 无锁编程
示例:
csharp
复制
private int _counter = 0;void IncrementCounter()
{Interlocked.Increment(ref _counter);
}
选择锁的指导原则
- 简单优先:优先使用最简单的
lock
关键字,除非有特殊需求 - 范围考虑:进程内同步优先选择轻量级锁,跨进程才用
Mutex
或命名Semaphore
- 读写比例:读多写少考虑
ReaderWriterLockSlim
- 等待时间:短时间等待考虑
SpinLock
,长时间等待用Monitor
或Mutex
- 并发控制:需要限制并发数用
SemaphoreSlim
- 原子操作:简单操作优先考虑
Interlocked
避免常见问题
- 死锁:避免嵌套锁,按固定顺序获取多个锁
- 锁粒度:锁的粒度要适中,过大影响并发,过小增加开销
- 锁持续时间:尽量减少持有锁的时间
- 递归锁:谨慎使用递归锁,可能导致意外行为
- 异常处理:确保在finally块中释放锁