仅使用关键部分的读/写锁定会导致死锁

Read/Write lock using only critical section causes deadlock

本文关键字:锁定 写锁 死锁 键部      更新时间:2023-10-16

在用相同的标题和答案浏览了这个问题之后,我想尝试一些应该只使用关键部分才能真正工作的东西,因此应该比现有解决方案快得多(使用其他内核对象,如互斥锁或信号量(

以下是我的读/写锁定/解锁功能:

#include <windows.h>
typedef struct _RW_LOCK 
{
    CRITICAL_SECTION readerCountLock;
    CRITICAL_SECTION writerLock;
    int readerCount;
} RW_LOCK, *PRW_LOCK;
void InitLock(PRW_LOCK rwlock)
{
    InitializeCriticalSection(&rwlock->readerCountLock);
    InitializeCriticalSection(&rwlock->writerLock);
}
void ReadLock(PRW_LOCK rwlock)
{
    EnterCriticalSection(&rwlock->readerCountLock); // In deadlock 1 thread waits here (see description below)
    if (++rwlock->readerCount == 1) 
    {
        EnterCriticalSection(&rwlock->writerLock); // In deadlock 1 thread waits here
    }
    LeaveCriticalSection(&rwlock->readerCountLock);
}
void ReadUnlock(PRW_LOCK rwlock)
{
    EnterCriticalSection(&rwlock->readerCountLock);
    if (--rwlock->readerCount == 0) 
    {
        LeaveCriticalSection(&rwlock->writerLock);
    }
    LeaveCriticalSection(&rwlock->readerCountLock);
}
int WriteLock(PRW_LOCK rwlock)
{
    EnterCriticalSection(&rwlock->writerLock); // In deadlock 3 threads wait here
}
void WriteUnlock(PRW_LOCK rwlock)
{
    LeaveCriticalSection(&rwlock->writerLock);
}

这是一个线程函数。从main调用InitLock (&g_rwLock);后,我创建了五个线程来尝试这些锁。

void thread_function()
{
    static int value = 0;
    RW_LOCK g_rwLock;
    
    while(1)
    {
        ReadLock(&g_rwlLock);
        BOOL bIsValueOdd = value % 2;
        ReadUnlock(&g_rwlLock);
        WriteLock(&g_rwlLock);
        value ++;
        WriteUnlock(&g_rwlLock);
    }
}

理想情况下,此代码应该永远运行而不会遇到任何问题。但令我失望的是,它并不总是运行。有时它会陷入僵局。我编译了这个并在Windows XP上运行它。要使用线程池创建线程,我使用的是第三方库。因此,这里不能给出所有涉及大量初始化例程和其他东西的代码。

但是为了缩短故事,我想知道是否有人通过查看上面的代码可以指出这种方法有什么问题?

我在上面的代码中评论了,当死锁发生时,每个线程(五个线程中(都在等待。(我通过将调试器附加到死锁进程来发现它(

任何输入/建议都非常棒,因为我已经坚持了很长时间了(贪婪地让我的代码运行得比以往任何时候都快(。

到目前为止发现了

两件事:

  • 初始化每个线程中的关键部分,这是不允许的(行为未定义(
  • 您不能将关键部分保留在与进入该部分的线程不同的线程中("如果线程在没有指定关键部分对象的所有权时调用LeaveCriticalSection,则会发生错误,可能导致另一个使用 EnterCriticalSection 的线程无限期等待。

后者符合你看到的僵局。

一旦你同时有多个读取器,你就无法控制它们调用ReadUnlock的顺序,所以你不能确保第一个线程,这是唯一允许调用LeaveCriticalSection的线程,是最后一个输出的线程。

这样

它就无法正常运行。

  • 让 1 个线程进入 ReadLock((,让它通过 ++ 指令,但在进入编写器 CS 之前暂停它
  • 另一个线程进入 WriteLock(( 并成功进入 writerCS

所以现在我们有读者计数 = 1 并同时运行写入器。 请注意,读取器在 EnterCriticalSection(&rwlock->writerLock( 上死锁