如何解决数组场景下的多线程死锁问题(C#面试真题)
死锁面试题解法
基础固定顺序双锁死锁解决
原题代码如下:
lock(a) { lock(b) { // do smth } } lock(b) { lock(a) { // do smth } }
该场景死锁的核心原因是两个线程的锁获取顺序相反,形成循环等待。你回答的「统一锁获取顺序」是正确解法:提前约定所有线程都按固定规则(如锁ID升序、哈希值升序)获取锁,即可破坏循环等待条件,避免死锁。
随机双锁场景死锁自动解决方案
原题伪代码如下:
function void foo(T[] arr) { lock(arr[randint(arr.length)]) { lock(arr[randint(arr.length)]) { // do smth } } } var arr = new arr{...} async () => { foo(arr) foo(arr) }
该场景的问题是锁获取顺序完全随机,仍可能出现线程1持有锁A等锁B、线程2持有锁B等锁A的死锁,本质还是锁获取顺序不一致的问题,有两类解决思路:
通用解法:自动排序锁获取顺序
不需要业务手动干预顺序,代码内部自动对要获取的两个锁做排序,永远先拿排序靠前的锁,再拿靠后的锁,即可保证所有线程的锁获取顺序一致。修改后的伪代码如下:
function void foo(T[] arr) { var lockObj1 = arr[randint(arr.length)]; var lockObj2 = arr[randint(arr.length)]; // 特殊处理两次随机到同一个对象的场景,避免重复加锁 if (lockObj1 == lockObj2) { lock(lockObj1) { // do smth } return; } // 按哈希值排序确定顺序,也可使用自定义的锁唯一ID排序 var firstLock = lockObj1.GetHashCode() > lockObj2.GetHashCode() ? lockObj2 : lockObj1; var secondLock = lockObj1.GetHashCode() > lockObj2.GetHashCode() ? lockObj1 : lockObj2; lock(firstLock) { lock(secondLock) { // do smth } } }
该方案性能更高,无重试开销,要求锁对象存在可比较的唯一标识即可。
C#专属解法:使用Monitor.TryEnter超时回退重试
C#中lock关键字是Monitor.Enter的语法糖,你可以手动调用Monitor.TryEnter指定超时时间,如果拿不到第二个锁就主动释放已持有的锁,回退后重试,破坏死锁的持有等待条件。示例代码如下:
void Foo<T>(T[] arr) where T : class { while (true) { var lock1 = arr[Random.Shared.Next(arr.Length)]; var lock2 = arr[Random.Shared.Next(arr.Length)]; // 两次随机到同一个对象的特殊处理 if (lock1 == lock2) { lock (lock1) { // 执行业务逻辑 return; } } bool lock1Taken = false; bool lock2Taken = false; try { // 尝试获取第一个锁,超时时间可自行调整 Monitor.TryEnter(lock1, TimeSpan.FromMilliseconds(100), ref lock1Taken); if (!lock1Taken) continue; // 尝试获取第二个锁 Monitor.TryEnter(lock2, TimeSpan.FromMilliseconds(100), ref lock2Taken); if (!lock2Taken) continue; // 两个锁都获取成功,执行业务逻辑 return; } finally { // 释放已拿到的锁 if (lock2Taken) Monitor.Exit(lock2); if (lock1Taken) Monitor.Exit(lock1); } } }
该方案不需要提前给锁对象设置可比较的唯一标识,适配性更强,极端情况下可能出现活锁,可通过增加随机重试延迟缓解。
内容的提问来源于stack exchange,提问作者Geara0
相关产品推荐
相关产品推荐

