如何调试读写器锁的死锁?

How do I debug a deadlock of readers-writer locks?

本文关键字:死锁 读写器 何调试 调试      更新时间:2023-10-16

我正在编写一个程序,该程序有一个线程将点文件读取到缓冲区中,还有许多线程从缓冲区中获取点并构造它们的八叉树。八叉树的每个立方体都受到读写器锁(又名shared_mutex(的保护,其中有 67 个(如果有两个线程,现在有(。如果文件太大,程序就会死锁,我在尝试调试它时会感到困惑。其中一个锁在 gdb 中如下所示:

[6] = {_M_impl = {_M_rwlock = {__data = {__readers = 1, 
__writers = 0, __wrphase_futex = 1, __writers_futex = 0, __pad3 = 0, 
__pad4 = 0, __cur_writer = 0, __shared = 0, __rwelision = 0 '00', 
__pad1 = "000000000000", __pad2 = 0, __flags = 0}, 
__size = "010000000000000001", '00' <repeats 46 times>, __align = 1}}},

大多数互斥体的__readers=1,一个__readers=3,一个__readers=4294967289左右。这是没有意义的,因为只有两个线程,所以只有两个线程可以读取它们;在 building-octree 阶段,它们应该是写锁定而不是读锁互斥锁,而 -7 看起来像是七个线程在没有先读锁定的情况下读解锁了互斥锁。尝试在__readers上设置观察点是行不通的;它使调试器崩溃,或类似的东西。

我写了一个关于锁定和解锁的包装器:

void lockBlockR(int block)
{
metaMutex.lock();
modReaders[block%modMutexSize]++;
metaMutex.unlock();
modMutex[block%modMutexSize].lock_shared();
}
void lockBlockW(int block)
{
modMutex[block%modMutexSize].lock();
}
void unlockBlockR(int block)
{
metaMutex.lock();
if (--modReaders[block%modMutexSize]<0)
cout<<"Read-unlocked "<<block<<" too many timesn";
metaMutex.unlock();
modMutex[block%modMutexSize].unlock_shared();
}
void unlockBlockW(int block)
{
modMutex[block%modMutexSize].unlock();
}

当程序挂起时,我查看了modReader,它都是零,然后是modMutex,它再次具有大部分__readers = 1和一个负数。我如何弄清楚发生了什么?

我正在运行Eoan Ermine,Linux 5.3.0和libc 2.30。该程序在 C++17 中使用 gcc 9.2.1 编译。

我以前在 PerfectTIN (https://github.com/phma/perfecttin( 中使用过读写器锁和锁的模池,但模池中的锁是普通的互斥锁。

ETA:我添加了另一个名为modWriters的整数映射和一些调试语句,并在解锁它未锁定的互斥锁时捕获了一个线程。不过,它是写锁定和写解锁,所以这并不能解释为什么__readers搞砸了。

如何调试读写器锁的死锁?

考虑使用 valgrind、GCC 10 静态分析选项和检测选项,如-fsanitize=thread和 Clang 静态分析器。

从源代码构建GCC 10是值得的。

请注意,并不总是能够静态可靠地检测所有死锁(赖斯定理(。阅读本报告草案。你可能会有海森虫。

也许使用C++线程库,特别是std::lock_guard

您可能更喜欢std::recursive_mutex而不是std::mutex,即使递归互斥体更慢更重(有些人说应该避免它们(。我的观点是,它们通常更安全。

您可以考虑使用 POCO 或 Qt 或 GtkMM 库的多线程功能。

注意 futex(7(,这是 Linux 上锁定的基本块。您可以使用 strace(1((和 pipe(7( 进行线程间通信或与 poll(2( 同步;另请参见 eventfd(2((