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

问题描述:

经过this question with the same title及其答案后,我想尝试一些应该只使用临界区域才能真正起作用的东西,因此速度应该快于现有的解决方案(也使用其他内核对象像互斥或信号)只使用关键部分的读/写锁会导致死锁

这里是我的读/写锁定/解锁功能:

#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上运行它。要使用线程池创建线程,我正在使用第三方库。因此,在这里不能给出所有涉及大量初始化例程和其他内容的代码。

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

我已经在上面的代码中注释了每个线程(出于五个线程)在死锁发生时一直等待。 (我发现通过附加调试器到死锁进程)

任何输入/建议将是非常好的,因为我坚持了很长一段时间现在(在使我的代码比以往更快运行的贪婪) 。

+0

请注意,这是一个读者优先锁定 - 如果您的读者数量从未达到0,写入将永远等待。如果这是不可取的,你可能想要考虑一个票务系统,如[Mellor-Crummey-Scott](http://www.cs.rochester.edu/u/scott/papers/1991_PPoPP_read_write.pdf)“公平“的锁定。 – 2014-12-01 20:10:58

+0

与往常一样:_RW_LOCK是为编译器保留的名称(前导下划线后跟大写字母) – 2014-12-01 20:11:12

+1

请发布演示此问题的*实际*代码。这里的代码显然已经在翻译中失去了一些东西,正在浪费有用的人的时间。 – 2014-12-01 21:03:07

斑点至今两件事情:

  • 您初始化每个线程,这是不允许的关键部分(行为是不确定的)
  • 从不同的线程不能离开一个关键部分即进入它的人(“如果一个线程调用LeaveCriticalSection,当它没有指定的临界区对象的所有权,发生错误时可以使用EnterCriticalSection无限期地等待引起另一个线程。”)

后者符合你看到的僵局。

一旦您同时有多个阅读器,您不能控制他们调用ReadUnlock的顺序,所以您不能确保第一个线程(允许调用LeaveCriticalSection的唯一一个线程)是最后一个线程。

+0

注意每个线程都有不同的关键部分。很明显,这不是他想要的,但它意味着'LeaveCriticalSection'在一个单独的线程不会造成死锁。 – 2014-12-01 20:23:01

+0

@Cory:哦,好吧,发现了。在这种情况下,什么都不应该阻止,并且“价值”上存在竞争条件。 UB每个标准,但不会在Windows上出现这些症状。 – 2014-12-01 20:50:48

+0

其实,想一想,什么是'价值'?与“价值”不一样。所以我现在不知道我们可以信任的代码,以及我们不能。如果实际代码中实际上只有一个关键部分,也许它只是一次,但我的第二点仍然适用。 – 2014-12-01 21:03:10

这种方式无法正确运行。

  • 允许1个线程进入ReadLock(),让它通过++指令,但进入作家CS
  • 另一个线程进入WriteLock()和successfuly进入writerCS

所以现在我们之前暂停读者数= 1,并同时运行作者。请注意,读卡器在EnterCriticalSection上是死锁的(& rwlock-> writerLock)

+0

这意味着读者将被*,直到作者离开关键部分。但是,提问者观察到的僵局是什么阻止了作者? – 2014-12-01 20:15:50

+0

也许你是对的,我错过了解释计数器。 – Anonymous 2014-12-01 20:22:41