单写/多读锁死的原因是什么?

6

我有一个类实例,被其他线程中的几个类用于通信。

这个类使用一个轻量级读写锁(WinAPI的SRWLOCK)作为同步对象,并使用几个RAII辅助类来实际锁定/解锁它:

static unsigned int readCounter = 0;

class CReadLock
{
public:
    CReadLock(SRWLOCK& Lock) : m_Lock(Lock) { InterlockedIncrement(&readCounter); AcquireSRWLockShared(&m_Lock);  }
    ~CReadLock() {ReleaseSRWLockShared(m_Lock); InterlockedDecrement(&readCounter);}

private:
    SRWLOCK& m_Lock;
};

class CWriteLock
{
public:
    CWriteLock(SRWLOCK& Lock) : m_Lock(Lock) { AcquireSRWLockExclusive(&m_Lock); }
    ~CWriteLock() { ReleaseSRWLockExclusive(&m_Lock); }

private:
    SRWLOCK& m_Lock;
};

问题是整个程序都死锁了。当我暂停死锁的程序时,我看到:
  • 一个线程被卡在 AcquireSRWLockExclusive() 中;
  • 两个线程被卡在 AcquireSRWLockShared() 中;
  • readCounter 全局变量设置为3。
据我所知,造成这种情况的唯一可能原因是 CReadLock 实例的析构函数某些地方没有被调用,因此锁被永久卡住了。然而,这种情况发生的唯一可能原因(就我所知)是由于已经抛出了异常。但事实上并没有抛出异常。我进行了检查。
可能的问题是什么?我应该如何修复(或至少定位原因)?
2个回答

9
你是否在递归方式中使用了读锁?
void foo()
{
    CReadLock rl(m_lock);
    ...
    bar();
}
void bar()
{
    CReadLock rl(m_lock);
    ...
}
void baz()
{
    CWritedLock rl(m_lock);
    ...
}

如果同时调用foo()baz()可能会导致死锁:

1. (Thread A) foo locks
2. (Thread B) baz asks to create write lock now all read locks would block until all are released - waits.
3. (Thread A) bar tries to lock and waits because there is pending write lock

如果您有2个线程被卡在读锁上,并且读锁计数器为3,很可能显示一个锁中有递归 - 即一个线程尝试两次获取读锁。


bar应该只获取锁,对吗?而且如果foo调用baz可能会发生死锁(不知道锁是否可递归)。 - Lol4t0
дёҚеҝ…жӢ…еҝғпјҢWin32 APIзҡ„AcquireSRWLockSharedдёҺPOSIXзҡ„pthread_rwlock_rdlockжҲ–EnterCriticalSectionдёҚеҗҢпјҢе®ғдёҚжҳҜйҖ’еҪ’зҡ„гҖӮ - Artyom
你能证明“baz请求现在创建写锁,所有读锁都会阻塞直到所有锁都被释放-等待”吗?因为MSDN :“没有关于请求所有权的线程获得所有权的顺序的保证;SRW锁既不公平也不FIFO。” - Lol4t0
@Lol4t0 这是读写锁的工作方式,为了防止写操作饥饿,一旦请求写锁,就不能获取读锁,读锁将被阻塞,直到所有读锁都被释放。这是常见的实现方式,也是 Win32 API SRWLock 的实现方式。 - Artyom
@Artyom 是的,那似乎就是问题所在了。我将 SRWLock 更改为简单的 CRITICAL_SECTION,现在一切都按预期工作了。谢谢! - obamator
显示剩余3条评论

3
one thread stuck in AcquireSRWLockExclusive();
two threads stuck in AcquireSRWLockShared();
readCounter global is set to 3.

据我所读,您目前有一个线程持有读锁,一个写线程正在等待该读锁被释放,还有两个读线程正在等待该写线程获取并释放锁。

换句话说,您有一个悬空的读线程,它尚未被销毁,就像您自己所说的那样。在析构函数和构造函数中添加调试打印信息。


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