C++ 仅使用临界段的读/写锁会导致死锁

C++ 仅使用临界段的读/写锁会导致死锁,c++,windows,multithreading,critical-section,thread-synchronization,C++,Windows,Multithreading,Critical Section,Thread Synchronization,在阅读了这些内容及其答案之后,我想尝试一些只在使用critical section时才有效的方法,因此应该比现有的解决方案(使用其他内核对象,如互斥或信号量)快得多 以下是我的读/写锁定/解锁功能: #include <windows.h> typedef struct _RW_LOCK { CRITICAL_SECTION readerCountLock; CRITICAL_SECTION writerLock; int readerCount; } R

在阅读了这些内容及其答案之后,我想尝试一些只在使用critical section时才有效的方法,因此应该比现有的解决方案(使用其他内核对象,如互斥或信号量)快得多

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

#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);
}
理想情况下,这段代码应该永远运行,没有任何问题。但令我失望的是,它并不总是运行。有时它会陷入僵局。我编译了这个,并在WindowsXP上运行了它。要使用threadpool创建线程,我使用的是第三方库。因此,这里无法给出所有涉及大量初始化例程和其他内容的代码

但简而言之,我想知道是否有人通过查看上面的代码可以指出这种方法的错误

我在上面的代码中注释过,当死锁发生时,每个线程(五个线程中的一个)都在等待。(我是通过将调试器附加到死锁进程中发现的)


任何输入/建议都会非常好,因为我已经为此坚持了很长一段时间(贪婪地想让我的代码比以往运行得更快)。

到目前为止发现了两件事:

  • 初始化每个线程中的关键部分,这是不允许的(行为未定义)
  • 您不能将关键部分保留在与输入它的线程不同的线程中(“如果一个线程在没有指定关键部分对象的所有权时调用
    LeaveCriticalSection
    ,则会发生一个错误,可能会导致另一个使用
    EnterCriticalSection
    的线程无限期等待。”)
后者符合你所看到的僵局


一旦您同时拥有多个读卡器,您就无法控制它们调用
ReadUnlock
的顺序,因此您无法确保中的第一个线程(唯一允许调用
LeaveCriticalSection
的线程)是最后一个线程。

这样它就无法正常运行

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

现在我们有了readers count=1,同时运行writer。请注意,读卡器在EnterCriticalSection上处于死锁状态(&rBlock->writerLock)

请注意,这是一个读卡器首选的锁——如果读卡器计数从未达到0,写入操作将永远等待。如果这是不可取的,您可能希望将票务系统视为“公平”锁。通常:_RW_lock是为编译器保留的名称(前导下划线后跟大写字母),请发布演示此问题的实际代码。这里的代码显然在翻译过程中丢失了一些东西,浪费了有用的人的时间。
InitLock
是从
main
调用的。我意识到在发布时发生了错误。我真的很紧张:(让我试着编写不会使用任何第三方库的代码,并将其发布在这里。现在谜团已经解开了(阿拉·史蒂夫的回答)请注意,如果您在追求性能,那么使用联锁操作来增加和减少读取器计数将大大快于关键部分。这意味着读取器将被阻止,直到编写器离开关键部分。但是,在提问者观察到的死锁中,是什么阻止了编写器?可能您是对的,我没有解释计数器。注意每个线程都有一个不同的临界区。显然这不是他想要的,但这意味着在一个单独的线程上的
LeaveCriticalSection
不会导致死锁。@Cory:哦,是的,很好地发现了。在这种情况下,任何东西都不会阻止,并且
value
上有一个争用条件他是标准的,但不会在Windows上出现这些症状。事实上,想想看,什么是
Value
?与
Value
不一样。所以我现在不知道我们对显示的代码可以信任什么,什么是不能信任的。如果真正的代码中只有一个关键部分,可能只初始化了一次,但我的第二点仍然可以apply.
InitLock
实际上是从
main
调用的。但是我怀疑你的第二点在这里是适用的。在一个线程调用
ReadLock
ReadUnlock
之后,如果另一个线程在前面调用
WriteLock
WriteUnlock
,它实际上调用了
LeaveCriticalSection
>在它从未进入的临界截面对象上。
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);
    }
}