如何调试读写锁死锁?

Pie*_*bat 0 c++ multithreading c++17

我正在编写一个程序,它有一个线程将一个点文件读入缓冲区,许多线程从缓冲区中获取点并构造它们的八叉树。八叉树的每个立方体都由一个读写锁(又名 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 '\000', 
          __pad1 = "\000\000\000\000\000\000", __pad2 = 0, __flags = 0}, 
        __size = "\001\000\000\000\000\000\000\000\001", '\000' <repeats 46 times>, __align = 1}}},
Run Code Online (Sandbox Code Playgroud)

大多数互斥锁有 __readers=1,一个有 __readers=3,一个有 __readers=4294967289 左右。这是没有意义的,因为只有两个线程,所以只有两个线程可以读取它们;在构建八叉树阶段,它们应该是写锁定而不是读锁定互斥锁,并且 -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 times\n";
  metaMutex.unlock();
  modMutex[block%modMutexSize].unlock_shared();
}

void unlockBlockW(int block)
{
  modMutex[block%modMutexSize].unlock();
}
Run Code Online (Sandbox Code Playgroud)

当程序挂起时,我查看了 modReaders,它全为零,然后查看 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:我添加了另一个称为 int 的映射modWriters和一些调试语句,并在解锁未锁定的互斥锁的过程中捕获了一个线程。不过,它是写锁定和写解锁,所以这并不能解释为什么__readers搞砸了。

Bas*_*tch 5

如何调试读写锁死锁?

考虑使用valgrind、GCC 10静态分析选项和仪器选项,例如-fsanitize=threadClang 静态分析器

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

请注意,并非总是可以静态且可靠地检测所有死锁(赖斯定理)。阅读这份报告草稿。你可能有heisenbugs

可能使用 C++线程库,特别是std::lock_guard

你可能更喜欢std::recursive_mutexstd::mutex即使递归互斥体速度较慢和更重的(有些人说,他们应避免)。我的观点是它们通常更安全。

您可以考虑使用POCOQtGtkMM库的多线程功能。

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