c# ReaderWriterLockSlim 在调用 ExitReadLock 的中断线程后损坏

本文关键字:中断 线程 损坏 ExitReadLock ReaderWriterLockSlim 调用 | 更新日期: 2023-09-27 18:04:34

我遇到了一个场景,ReaderWriterLockSlim在一系列法律诉讼后似乎被破坏了。

流程为:

  1. 线程 1 采用写入器锁 1
  2. 线程 2 尝试获取读取器锁定 1 - 块
  3. 线程 2 中断,并调用 lock1。退出读取锁定线程 2 未获得锁。似乎应该抛出一个例外。
  4. 线程 1 退出锁 1 的写入器锁
  5. 任何尝试获取 lock1 的线程。EnterReadLock将永远阻止

在上述阶段 3 之后,调试器将显示该 lock1。CurrentReadCount 已损坏 - 似乎已溢出到0x7FFFFFF。

我想知道是否有人遇到过这种情况,或者我错过了什么。

重现它的代码:

[TestMethod]
public void ReproTest()
{
    var rwlock = new ReaderWriterLockSlim();
    rwlock.EnterWriteLock();
    bool taken = false;
    var reader = new Thread(() =>
    {
        try
        {
            rwlock.EnterReadLock();
            s_logger.Info("Enter");
        }
        catch (ThreadInterruptedException)
        {
            rwlock.ExitReadLock();
        }
    });
    reader.Name = "Reader";
    reader.Start();
    Thread.Sleep(1000);
    reader.Interrupt();
    Thread.Sleep(1000);
    rwlock.ExitWriteLock();
    while (!taken)
    {
        taken = rwlock.TryEnterReadLock(1000);
    }
    Thread.Sleep(1000);
}

c# ReaderWriterLockSlim 在调用 ExitReadLock 的中断线程后损坏

这看起来像框架中的一个错误(在 v3.5 和 v4.0 上测试(。ExitReadLock()应该抛出一个SynchronizationLockException,但在这种情况下不会。实际上,您可以通过以下方法更轻松地触发非常相似的问题:

rwlock.EnterReadLock();
rwlock.ExitReadLock();
// This should throw a SynchronizationLockException but doesn't
rwlock.ExitReadLock();
// At this point, rwlock.CurrentReaderCount = 0x0fffffff

(事实上,如果在之前进入锁的任何线程上没有匹配EnterReadLock()的情况下调用锁,ExitReadLock()会损坏锁。

仅当使用无参数构造函数或使用 LockRecursionPolicy.NoRecursion 创建ReaderWriterLockSlim时,才会出现此问题。如果用LockRecursionPolicy.SupportsRecursion创建,它不会被不匹配的ExitReadLock()破坏。

如果您预计读取器线程在

等待进入锁定时被中断,我建议将读取器线程方法更改为:

var reader = new Thread(() =>
{
    var entered = false;
    try
    {
        rwlock.EnterReadLock();
        entered = true;
        s_logger.Info("Enter");
    }
    finally
    {
        if (entered) rwlock.ExitReadLock();
    }
});

读取器永远不会进入读锁定。 它坐在那里等待写入被释放。 当它被中断时,即使你从未进入过,你也会尝试退出,导致读取计数低于 0 我想:)

修复

@Lasse和@Jeremy指出的内容的代码:

static public void ReproTest()
{
    var rwlock = new ReaderWriterLockSlim();
    rwlock.EnterWriteLock();
    s_logger.Info("0:Enter");
    bool taken1 = false;
    var reader = new Thread(() =>
    {
        try
        {
            rwlock.EnterReadLock();
            s_logger.Info("1:Enter");
            // only set to true if taken
            taken1 = true;
        }
        catch (ThreadInterruptedException)
        {
            // only release if taken
            if (taken1)
                rwlock.ExitReadLock();
            taken1 = false;
        }
    });
    reader.Name = "Reader";
    reader.Start();
    Thread.Sleep(1000);
    reader.Interrupt();
    Thread.Sleep(1000);
    rwlock.ExitWriteLock();
    // 2nd taken variable here only so we can see state of taken1
    bool taken2 = taken1;
    while (!taken2)
    {
        taken2 = rwlock.TryEnterReadLock(1000);
        s_logger.Info("2:Enter");
    }
    Thread.Sleep(1000);
}

运行时,调试输出正确显示正在获取的写锁定、未创建的第 1 个读锁定和已采用的第二个读锁定:

0:Enter
A first chance exception of type 'System.Threading.ThreadInterruptedException' occurred in mscorlib.dll
The thread 'Reader' (0x1358) has exited with code 0 (0x0).
2:Enter