C# 现代化锁的最佳实践

提示:本资料由 [Leon-Kay] 个人总结与资料整理而成,仅供个人学习使用,难免存在疏漏之处,恳请各位大佬批评指正,不胜感激!



四大主流锁机制对比

1. System.Threading.Lock (C# 13)

csharp 复制代码
private readonly Lock _lock = new Lock();

public void DoWork()
{
    using (_lock.EnterScope())  // 或 lock(_lock)
    {
        // 临界区代码
    }
}

✅ 优势:

  • 性能最优(比传统lock快)
  • 自动内存管理
  • 更好的调试信息
  • 编译器优化支持

❌ 劣势:

  • 只能C# 13使用
  • 仍然是互斥锁(一次只能一个线程)

🎯 适用场景: 99%的普通同步需求

2. ReaderWriterLockSlim

csharp 复制代码
private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim();

public string Read()
{
    _rwLock.EnterReadLock();
    try { return _data; }
    finally { _rwLock.ExitReadLock(); }
}

public void Write(string value)
{
    _rwLock.EnterWriteLock();
    try { _data = value; }
    finally { _rwLock.ExitWriteLock(); }
}

✅ 优势:

  • 多个线程可同时读
  • 读写分离,提高并发度
  • 适合读多写少场景

❌ 劣势:

  • 开销比普通锁大
  • 写操作仍需独占
  • 可能出现写饥饿

🎯 适用场景: 缓存、配置、读多写少的数据结构

3. 信号量 (Semaphore/SemaphoreSlim)

csharp 复制代码
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(3, 3); // 允许3个并发

public async Task ProcessAsync()
{
    await _semaphore.WaitAsync();
    try
    {
        // 限制并发数的工作
    }
    finally
    {
        _semaphore.Release();
    }
}

✅ 优势:

  • 控制并发数量
  • 支持异步等待
  • 灵活的资源管理

❌ 劣势:

  • 不保护共享状态
  • 需要手动管理计数
  • 可能资源泄露

🎯 适用场景: 限流、连接池、资源控制

4. 无锁 (ConcurrentXXX)

csharp 复制代码
private readonly ConcurrentDictionary<string, int> _dict = new();
private int _counter;

public void Increment()
{
    Interlocked.Increment(ref _counter);  // 原子操作
}

public void AddItem(string key, int value)
{
    _dict.AddOrUpdate(key, value, (k, old) => old + value);
}

✅ 优势:

  • 性能最高
  • 无死锁风险
  • 高并发友好

❌ 劣势:

  • 复杂操作难实现
  • 学习成本高
  • ABA问题等陷阱

🎯 适用场景: 高性能场景、简单数据操作

🚀 现代化最佳实践

选择决策树

csharp 复制代码
### 实际场景模板

```csharp
public class ModernService
{
    // 缓存 - 优先无锁
    private readonly ConcurrentDictionary<string, object> _cache = new();

    // 计数器 - 原子操作
    private long _requestCount;
    public void IncrementRequests() => Interlocked.Increment(ref _requestCount);

    // 限流 - 信号量
    private readonly SemaphoreSlim _concurrencyLimit = new(10, 10);

    // 复杂状态 - 新Lock
    private readonly Lock _stateLock = new();
    private ComplexState _state = new();

    // 配置管理 - 读写锁
    private readonly ReaderWriterLockSlim _configLock = new();
    private Dictionary<string, string> _config = new();
}

🛡️ 锁问题解决方案

死锁预防

1. 锁顺序化 - 最重要的规则

csharp 复制代码
// ❌ 错误:可能死锁
public class BadTransfer
{
    public void Transfer(Account from, Account to, decimal amount)
    {
        lock (from._lock)
        {
            lock (to._lock)  // 不同线程可能以相反顺序获取锁
            {
                from.Balance -= amount;
                to.Balance += amount;
            }
        }
    }
}

// ✅ 正确:按固定顺序获取锁
public class GoodTransfer
{
    public void Transfer(Account from, Account to, decimal amount)
    {
        // 按账户ID排序,确保锁获取顺序一致
        var firstLock = from.Id < to.Id ? from._lock : to._lock;
        var secondLock = from.Id < to.Id ? to._lock : from._lock;

        lock (firstLock)
        {
            lock (secondLock)
            {
                from.Balance -= amount;
                to.Balance += amount;
            }
        }
    }
}

2. 超时机制

csharp 复制代码
public class TimeoutLockService
{
    private readonly Lock _lock1 = new();
    private readonly Lock _lock2 = new();

    public bool TryExecuteWithTimeout(Action action, int timeoutMs = 5000)
    {
        using var cts = new CancellationTokenSource(timeoutMs);

        try
        {
            using (_lock1.EnterScope())
            {
                cts.Token.ThrowIfCancellationRequested();
                using (_lock2.EnterScope())
                {
                    action();
                    return true;
                }
            }
        }
        catch (OperationCanceledException)
        {
            return false; // 超时
        }
    }
}

3. 避免嵌套锁

csharp 复制代码
// ❌ 危险:嵌套锁
public void BadMethod()
{
    lock (_lock1)
    {
        DoSomething();
        lock (_lock2)  // 嵌套锁容易死锁
        {
            DoMore();
        }
    }
}

// ✅ 安全:分离锁操作
public void GoodMethod()
{
    var data1 = GetDataSafely1();
    var data2 = GetDataSafely2();

    // 在锁外处理数据
    var result = ProcessData(data1, data2);

    SetResultSafely(result);
}

性能优化技巧

1. 减少锁粒度

csharp 复制代码
// ❌ 锁粒度太大
public class CoarseGrainedLock
{
    private readonly Lock _bigLock = new();
    private readonly Dictionary<string, UserData> _users = new();

    public void UpdateUser(string userId, UserData data)
    {
        lock (_bigLock)  // 所有用户更新都要等待
        {
            _users[userId] = data;
        }
    }
}

// ✅ 细粒度锁
public class FineGrainedLock
{
    private readonly ConcurrentDictionary<string, Lock> _userLocks = new();
    private readonly ConcurrentDictionary<string, UserData> _users = new();

    public void UpdateUser(string userId, UserData data)
    {
        var userLock = _userLocks.GetOrAdd(userId, _ => new Lock());
        lock (userLock)  // 只锁定特定用户
        {
            _users[userId] = data;
        }
    }
}

2. 双重检查模式

csharp 复制代码
public class LazyInitialization
{
    private volatile ExpensiveObject _instance;
    private readonly Lock _lock = new();

    public ExpensiveObject GetInstance()
    {
        if (_instance == null)  // 第一次检查(无锁)
        {
            lock (_lock)
            {
                if (_instance == null)  // 第二次检查(有锁)
                {
                    _instance = new ExpensiveObject();
                }
            }
        }
        return _instance;
    }
}

异步友好模式

csharp 复制代码
public class AsyncSafeService
{
    private readonly SemaphoreSlim _asyncLock = new(1, 1);

    // ❌ 错误:在async方法中使用lock
    public async Task BadMethodAsync()
    {
        // lock (_lock)  // 编译错误:不能在async方法中使用lock
        // {
        //     await SomeAsyncWork();
        // }
    }

    // ✅ 正确:使用SemaphoreSlim
    public async Task GoodMethodAsync()
    {
        await _asyncLock.WaitAsync();
        try
        {
            await SomeAsyncWork();
        }
        finally
        {
            _asyncLock.Release();
        }
    }

    // ✅ 更好:封装成扩展方法
    public async Task<T> ExecuteWithLockAsync<T>(Func<Task<T>> operation)
    {
        await _asyncLock.WaitAsync();
        try
        {
            return await operation();
        }
        finally
        {
            _asyncLock.Release();
        }
    }
}
 csharp

📊 选择指南总结

场景 推荐方案 原因
简单计数器 Interlocked 无锁,性能最佳
字典缓存 ConcurrentDictionary 无锁,并发友好
单例模式 Lazy<T> 或双重检查 线程安全,延迟初始化
配置管理 ReaderWriterLockSlim 读多写少优化
限流控制 SemaphoreSlim 控制并发数
一般同步 System.Threading.Lock 现代化,性能优
异步场景 SemaphoreSlim 异步友好
文件操作 Lock + 异常处理 简单可靠

🎯 黄金法则

  1. 无锁 > 信号量(控制并发)> Lock(互斥)> 读写锁(读多写少)
  2. 能用ConcurrentXXX就别用锁
  3. 锁对象要专用,不要锁this或字符串
  4. 锁里面不要调用外部方法
  5. 异步方法用SemaphoreSlim,不用lock
  6. 获取多个锁时,总是按相同顺序
  7. 尽快释放锁,不要在锁内做耗时操作

选择原则:先考虑能否无锁,再考虑是否需要限流,最后才考虑互斥锁的具体类型。

欢迎访问我的 GitHub 查看更多相关项目

创作权保护

本文由 [Leon-Kay] 学习总结编写,水平有限,内容仅供参考,作为个人记录使用。若有疏漏,请不吝赐教。版权归作者所有,未经授权,禁止转载、摘编或以其他方式使用本文内容。如需合作或转载本文,请联系作者获得授权。

相关推荐
long31640 分钟前
构建者设计模式 Builder
java·后端·学习·设计模式
Noii.1 小时前
Spring Boot初级概念及自动配置原理
java·spring boot·后端
探索java1 小时前
Tomcat Server 组件原理
java·后端·tomcat
咕白m6251 小时前
通过 C# 高效提取 PDF 文本的完整指南
后端·c#
smallyu1 小时前
Go 语言 GMP 调度器的原理是什么
后端·go
掉头发的王富贵2 小时前
ShardingSphere-JDBC入门教程(上篇)
spring boot·后端·mysql
盖世英雄酱581362 小时前
必须掌握的【InheritableThreadLocal】
java·后端
LovelyAqaurius2 小时前
乐观锁及其实现方式详解
后端
绝无仅有2 小时前
编写 Go 项目的 Dockerfile 文件及生成 Docker 镜像
后端·面试·github
tager2 小时前
🍪 让你从此告别“Cookie去哪儿了?”
前端·javascript·后端