使用ReaderWriterLockSlim时出现神秘的死锁损坏问题

4

我写了一个相当简单的ReaderWriterLockSlim包装器:

class SimpleReaderWriterLock
{
    private class Guard : IDisposable
    {
        public Guard(Action action)
        {
            _Action = action;
        }

        public void Dispose()
        {
            _Action?.Invoke();
            _Action = null;
        }

        private Action _Action;
    }

    private readonly ReaderWriterLockSlim _Lock
        = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

    public IDisposable ReadLocked()
    {
        _Lock.EnterReadLock();
        return new Guard(_Lock.ExitReadLock);
    }

    public IDisposable WriteLocked()
    {
        _Lock.EnterWriteLock();
        return new Guard(_Lock.ExitWriteLock);
    }

    public IDisposable UpgradableReadLocked()
    {
        _Lock.EnterUpgradeableReadLock();
        return new Guard(_Lock.ExitUpgradeableReadLock);
    }
}

这可能不是最高效的方法,因此我很感兴趣改进这个类的建议。

使用方法如下:

using (_Lock.ReadLocked())
{
    // protected code
}

(有相当数量的读取操作非常频繁,几乎不会有任何写入操作。)

在发布模式和生产环境中,这似乎总是按预期工作。但是在调试模式和调试器中,进程偶尔会陷入一种奇怪的死锁状态——它已经调用了 EnterReadLock,但是锁本身没有被任何东西持有(所有者为 0,报告是否有任何读者/写者/等待者的属性均为否等),但是内部的自旋锁被锁定,并且一直在那里无限自旋。

我不知道是什么触发了这个问题,除了它似乎更容易发生如果我停在断点并单步执行(在完全不相关的代码中)。

如果我手动切换自旋锁 _isLocked 字段回到 0,那么进程就会恢复,并且之后似乎一切正常。

是代码或锁本身出了问题吗?调试器是否会意外地引发自旋锁死锁?(我正在使用 .NET 4.6.2。)

我阅读过一篇文章,指出 ThreadAbortException 可能会影响这些锁——我的代码确实在某些地方调用了 Abort(),但我不认为它们涉及调用到此锁定代码(尽管我可能错了)。如果问题是锁已被获取但从未释放,则应该与我看到的不同。(顺便说一下,框架文档明确禁止在受限制的区域内获取锁,正如在那篇文章中提倡的那样。)

我可以更改代码以避免锁间接性,但是使用卫兵是否是推荐的做法?


如果我调用了 WriteLocked 但忘记对其结果调用 Dispose,会发生什么? - mjwills
@mjwills 你本来期望的是:下一次读/写操作会发生死锁。但是这里发生的情况并非如此,因为外部锁看起来没有被锁定,而实际上也确实没有被锁定。 - Miral
1个回答

1

由于using语句不支持中断安全性, 您可以尝试使用链接文章中提供的支持中断安全性的解决方法来替换它。例如:

public void WithReadLock(Action action)
{
    var lockAcquired = false;
    try
    {
        try { }
        finally
        {
            _Lock.EnterReadLock();
            lockAcquired = true;
        }
        action();
    }
    finally
    {
        if (lockAcquired) _Lock.ExitReadLock();
    }
}

使用方法:

var locker = new SimpleReaderWriterLock();
locker.WithReadLock(() =>
{
    // protected code
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接