C#中的锁机制详解

发布于:2025-06-12 ⋅ 阅读:(17) ⋅ 点赞:(0)

在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);
}

选择锁的指导原则

  1. ​简单优先​​:优先使用最简单的lock关键字,除非有特殊需求
  2. ​范围考虑​​:进程内同步优先选择轻量级锁,跨进程才用Mutex或命名Semaphore
  3. ​读写比例​​:读多写少考虑ReaderWriterLockSlim
  4. ​等待时间​​:短时间等待考虑SpinLock,长时间等待用MonitorMutex
  5. ​并发控制​​:需要限制并发数用SemaphoreSlim
  6. ​原子操作​​:简单操作优先考虑Interlocked

避免常见问题

  1. ​死锁​​:避免嵌套锁,按固定顺序获取多个锁
  2. ​锁粒度​​:锁的粒度要适中,过大影响并发,过小增加开销
  3. ​锁持续时间​​:尽量减少持有锁的时间
  4. ​递归锁​​:谨慎使用递归锁,可能导致意外行为
  5. ​异常处理​​:确保在finally块中释放锁