读写锁允许在获取写锁时进行读取吗?

10

我有一个静态类,被多个远程调用和应用程序内的线程访问。这个类的部分功能是控制对各种文件的读写访问,因此我在文件列表上实现了一个静态ReaderWriterLock。该项目使用.NET Framework 2.0以满足客户需求。

但是,当我使用许多不同的客户端(通常使用16个)进行系统压力测试时,每个客户端都执行大量读写操作,然后在数小时甚至数天后,并且至少完成了500k+事务后系统会非常间歇性地崩溃。好吧,出现了一个bug..

但是当我检查所有锁定事件的日志时,我可以看到已经发生了以下情况:

1:线程A直接获取写锁,检查IsWriterLock显示为真。

2:线程B尝试获取读锁并成功,即使线程A仍然持有写锁

3:现在系统崩溃,堆栈跟踪现在显示ReaderWriterLock的空引用异常

之前已经运行了数十万次此过程而未出现错误,并且我可以检查日志并查看在所有先前的情况下读取锁定始终被阻止,直到写入退出。我还尝试将readerwriterlock实现为单例,但问题仍然存在。

以简化版本的readerwriterlock实现如下:

private const int readwriterlocktimeoutms = 5000;
    private static ReaderWriterLock readerWriterLock = new ReaderWriterLock();


    // this method will be called by thread A
    public static void MethodA()
    {
        // bool to indicate that we have the lock
        bool IsTaken = false;

        try
        {                
            // get the lock
            readerWriterLock.AcquireWriterLock(readwriterlocktimeoutms);

            // log that we have the lock for debug
            // Logger.LogInfo("MethodA: acquired write lock; writer lock held {0}; reader lock held {1}", readerWriterLock.IsWriterLockHeld.ToString(),readerWriterLock.IsReaderLockHeld.ToString(), );

            // mark that we have taken the lock
            IsTaken = true;
        }
        catch(Exception e)
        {
            throw new Exception(string.Format("Error getting lock {0} {1}", e.Message, Environment.StackTrace));
        }

        try
        {
           // do some work
        }
        finally
        {
            if (IsTaken)
            {
                readerWriterLock.ReleaseWriterLock();
            }
        }

    }

    // this method will be called by thread B
    public static void MethodB()
    {
        // bool to indicate that we have the lock
        bool IsTaken = false;

        try
        {
            // get the lock
            readerWriterLock.AcquireReaderLock(readwriterlocktimeoutms);

            // log that we have the lock for debug
            // Logger.LogInfo("MethodB: acquired read lock; writer lock held {0}; reader lock held {1}", readerWriterLock.IsWriterLockHeld.ToString(),readerWriterLock.IsReaderLockHeld.ToString(), );

            // mark that we have taken the lock
            IsTaken = true;
        }
        catch (Exception e)
        {
            throw new Exception(string.Format("Error getting lock {0} {1}", e.Message, Environment.StackTrace));
        }

        try
        {
           // do some work
        }
        finally
        {
            if (IsTaken)
            {
                readerWriterLock.ReleaseReaderLock();
            }
        }
    }

enter code here

能否完整地添加IsTaken=true语句? - H H
我不知道这是否能解释清楚问题,但那段代码是明显错误的;释放锁定的操作 必须 是第一个 try/finally 语句块的一部分 - 否则实际工作代码中的任何异常都将导致锁定未被释放。 - Marc Gravell
Marc - 不确定我是否同意你的观点。如果在工作代码中抛出任何异常,则必须在应用程序或线程终止之前始终调用finally方法。因此,锁定应始终被释放。 - Johnv2020
1
@JohnV2020 - 根据你的代码,看起来好像你在try-finally之外“做了一些工作”,因为注释是在try-finally之外的。我猜你的意思是你在try块内完成这项工作。 - CodeNaked
无论你是否获得锁定,你都在进行工作(在两种方法中)。这真的是你想要的吗? - Fantius
显示剩余7条评论
2个回答

9

@所有人,最终我们找到了解决这个问题的方法。@Yannick,你的思路是正确的...

如果MSDN说读写锁不能同时持有,则不可能。

今天我从微软那里得到了确认,在多处理器系统上出现极重负载的情况下(注意:我只在英特尔系统上无法复现此问题,而在AMD系统上可以),ReaderWriterLock类对象可能会损坏,如果任何时候编写者数量增加,这些编写者可能会在队列中备份,因此这种风险会增加。

在过去的两周中,我一直在使用.Net 3.5 ReaderWriterLockSlim类,并没有遇到这个问题,这与Microsoft确认的相符,即readerwriterlockslim类不存在与fat ReaderWriterLock类相同的损坏风险。


3
如果MSDN 不可能同时持有读取器和写入器锁。在您的进程中,是否有可能同时拥有2个readerWriterLock对象,出于其他原因?
另一件奇怪的事情是,使用isWriterLockHeld调试一个线程,而当前线程是读取器线程,无法让您知道另一个线程内的写入情况。您如何知道Thread A仍然持有写入锁,以及如何知道它不是由于调试日志系统延迟或“混合”线程给出的指令?
另一个想法是,其他共享资源是否导致死锁?这会导致某种崩溃吗?(虽然空异常仍然很奇怪,除非我们考虑死锁已清除并且readerWriterLock已重置。
您的问题很奇怪,确实。
还有一个问题,这不会解决您的问题。在调试应用程序时,您为什么要使用isTaken,而依赖于isWriterLockHeld(或isReaderLockHeld)?为什么不在finally块中使用它?

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