同步转异步时SemaphoreSlim与lock的使用粒度及死锁问题咨询
C#异步锁迁移:SemaphoreSlim死锁解决与锁粒度选择
一、解决SemaphoreSlim替代lock的死锁问题
lock关键字具备线程重入性——同一线程可以多次获取同一个lock锁,这意味着在lock块内触发事件,回调中再次访问需要锁的资源时不会死锁。但SemaphoreSlim不支持重入,直接替换lock会导致同一线程重复调用WaitAsync时阻塞死锁。
核心解决方案:事件触发移至锁外部
把事件触发逻辑从锁的保护范围内移出,确保回调执行时当前线程已经释放了锁,避免重入冲突。同时要提前复制事件引用,防止锁外触发时事件被多线程修改。
代码对比:
原同步lock代码(无死锁)
private readonly object _lockObj = new object(); public event EventHandler JobAdded; public void AddJob(Job job) { lock (_lockObj) { _jobs.Add(job); // 回调访问AllJobs时可重入lock,无死锁 JobAdded?.Invoke(this, EventArgs.Empty); } } public IReadOnlyList<Job> AllJobs { get { lock (_lockObj) { return _jobs.ToList().AsReadOnly(); } } }
错误的SemaphoreSlim替换(死锁)
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public async Task AddJobAsync(Job job) { await _semaphore.WaitAsync(); try { _jobs.Add(job); // 回调调用AllJobsAsync时,同一线程再次WaitAsync会阻塞死锁 JobAdded?.Invoke(this, EventArgs.Empty); } finally { _semaphore.Release(); } }
正确实现(事件移至锁外)
public async Task AddJobAsync(Job job) { EventHandler handler = null; await _semaphore.WaitAsync(); try { _jobs.Add(job); // 提前复制事件引用,避免多线程下事件被修改 handler = JobAdded; } finally { _semaphore.Release(); } // 锁外触发事件,回调获取锁时无重入冲突 handler?.Invoke(this, EventArgs.Empty); }
二、复杂方法的锁粒度选择与迁移模式
权衡点回顾
- 粗粒度锁:全局加锁(比如整个类用一个锁),优点是实现简单、开销低、死锁风险小;缺点是并发能力差,多个操作会互相阻塞。
- 细粒度锁:仅在访问受保护资源时局部加锁,优点是并发能力强;缺点是实现复杂,需要梳理资源依赖,容易因锁顺序不当引发死锁。
迁移建议模式
优先采用粗粒度锁完成初期迁移
对于遗留代码,先以粗粒度锁替代lock,快速保证异步场景下的线程安全。不需要深入梳理复杂的资源依赖,能最大程度减少迁移中的bug,适合先确保功能正确,再做性能优化。按需拆分细粒度锁
当粗粒度锁成为性能瓶颈时,再逐步拆分锁。拆分的核心原则:- 按独立资源组拆分:将关联性强的资源操作归为同一锁保护,比如Job的添加/修改用一个锁,只读查询用另一个锁(需注意数据一致性)。
- 严格遵循锁的获取顺序:如果需要获取多个锁,永远按固定顺序获取(比如先获取锁A,再获取锁B),避免交叉锁导致死锁。
异步场景的特殊优化
针对高并发的只读操作,可以考虑使用支持读写分离的锁(比如AsyncReaderWriterLock),允许多个读线程同时访问,写线程独占,平衡并发效率与线程安全。但要注意这类锁的重入规则,避免误用。封装锁逻辑减少重复代码
把异步锁的获取和释放封装成可复用的逻辑,比如用异步using模式,减少手动编写WaitAsync/Release的重复代码,避免遗漏释放锁:private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public async ValueTask<IDisposable> AcquireLockAsync() { await _semaphore.WaitAsync(); return new SemaphoreReleaser(_semaphore); } private class SemaphoreReleaser : IDisposable { private readonly SemaphoreSlim _semaphore; public SemaphoreReleaser(SemaphoreSlim semaphore) => _semaphore = semaphore; public void Dispose() => _semaphore.Release(); } // 使用方式 using (await AcquireLockAsync()) { // 操作受保护资源 }
内容的提问来源于stack exchange,提问作者bas
相关产品推荐
相关产品推荐

