【问题标题】:How do I debug a deadlock of readers-writer locks?如何调试读写锁死锁?
【发布时间】:2020-06-26 08:05:06
【问题描述】:

我正在编写一个程序,它有一个线程将点文件读入缓冲区,而许多线程从缓冲区中获取点并构造它们的八叉树。八叉树的每个多维数据集都由一个读写锁(又名 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}}},

大多数互斥锁的 __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();
}

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

【问题讨论】:

    标签: c++ multithreading c++17


    【解决方案1】:

    如何调试读写锁死锁?

    考虑使用valgrind、GCC 10 static analysis 选项和instrumentation 选项,例如-fsanitize=threadClang static analyzer

    值得从其源代码构建GCC 10

    请注意,并非总是可以静态可靠地检测到所有死锁 (Rice's theorem)。阅读this draft 报告。你可以有heisenbugs

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

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

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

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

    【讨论】:

    • 问题出在std::shared_mutex,而不是std::mutex。我用mutex 替换了所有shared_mutexes 并发现了一个明显的死锁:两个线程都在等待第8 个互斥锁,其中一个已经锁定并忘记解锁。不过,我仍然必须找出那是什么时候发生的。但是__readers 的数字仍然是个谜。我用过 Valgrind(Helgrind 和 DRD);它会产生很多噪音,因为受锁保护的一些东西是会改变大小的数据结构。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-01
    • 1970-01-01
    • 2015-04-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多