【问题标题】:`std::condition_var::notify_all` deadlocks`std::condition_var::notify_all` 死锁
【发布时间】:2023-01-04 10:57:56
【问题描述】:

我有一个线程生成的 cpp 代码,将数据推入队列,另一个线程在将数据传递给其他库进行处理之前使用它。

std::mutex lock;
std::condition_variable new_data;
std::vector<uint8_t> pending_bytes;
bool data_done=false;

// producer
  void add_bytes(size_t byte_count, const void *data)
  {
    if (byte_count == 0)
        return;

    std::lock_guard<std::mutex> guard(lock);
    uint8_t *typed_data = (uint8_t *)data;
    pending_bytes.insert(pending_bytes.end(), typed_data,
                               typed_data + byte_count);

    new_data.notify_all();
  }

  void finish()
  {
    std::lock_guard<std::mutex> guard(lock);

    data_done = true;
    new_data.notify_all();
  }

// consumer
Result *process(void)
{
  data_processor = std::unique_ptr<Processor>(new Processor());

  bool done = false;
  while (!done)
  {
    std::unique_lock<std::mutex> guard(lock);
    new_data.wait(guard, [&]() {return data_done || pending_bytes.size() > 0;});

    size_t byte_count = pending_bytes.size();
    std::vector<uint8_t> data_copy;
    if (byte_count > 0)
    {
      data_copy = pending_bytes; // vector copies on assignment
      pending_bytes.clear();
    }

    done = data_done;
    guard.unlock();

    if (byte_count > 0)
    {
      data_processor->process(byte_count, data_copy.data());
    }
  }

  return data_processor->finish();
}

其中 Processor 是一个相当复杂的类,有很多多线程处理,但据我所知,它应该与上面的代码分开。

现在有时代码会死锁,我正试图找出竞争条件。我最大的线索是生产者线程似乎卡在notify_all() 下。在 GDB 中,我得到以下回溯,显示 notify_all 正在等待某事:

[Switching to thread 3 (Thread 0x7fffe8d4c700 (LWP 45177))]

#0  0x00007ffff6a4654d in __lll_lock_wait () from /lib64/libpthread.so.0
#1  0x00007ffff6a44240 in pthread_cond_broadcast@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#2  0x00007ffff67e1b29 in std::condition_variable::notify_all() () from /lib64/libstdc++.so.6
#3  0x0000000001221177 in add_bytes (data=0x7fffe8d4ba70, byte_count=256,
    this=0x7fffc00dbb80) at Client/file.cpp:213

同时还拥有锁

(gdb) p lock
$12 = {<std::__mutex_base> = {_M_mutex = {__data = {__lock = 1, __count = 0, __owner = 45177, __nusers = 1, __kind = 0,
        __spins = 0, __elision = 0, __list = {__prev = 0x0, __next = 0x0}},

另一个线程在条件变量 wait 中等待

[Switching to thread 5 (Thread 0x7fffe7d4a700 (LWP 45180))]
#0  0x00007ffff6a43a35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
(gdb) bt
#0  0x00007ffff6a43a35 in pthread_cond_wait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
#1  0x00007ffff67e1aec in std::condition_variable::wait(std::unique_lock<std::mutex>&) () from /lib64/libstdc++.so.6
#2  0x000000000121f9a6 in std::condition_variable::wait<[...]::{lambda()#1}>(std::
unique_lock<std::mutex>&, [...]::{lambda()#1}) (__p=..., __lock=...,
    this=0x7fffc00dbb28) at /opt/rh/devtoolset-9/root/usr/include/c++/9/bits/std_mutex.h:104

Process数据部分下还有另外两个线程运行,它们也挂在pthread_cond_wait上,但据我所知,它们不共享任何同步基元(并且只是在等待调用processor-&gt;add_data或@ 987654334@) 知道notify_all 在等什么吗?或找到罪魁祸首的方法?

编辑:我在这里用虚拟处理器复制了代码: https://onlinegdb.com/lp36ewyRSP 但是,正如预期的那样,这并没有重现这个问题,所以我假设发生了一些更复杂的事情。可能只是不同的时间,但也许 condition_variableOpenMP (由真实处理器使用)之间的一些交互可能导致这个?

【问题讨论】:

  • 尝试将全局变量 data_done 初始化为 false
  • @Harry 抱歉,一切都在代码库中初始化,只是尝试在这里快速获取 sn-p
  • 请先提取一个minimal reproducible example。您尝试过但未能重现问题这一事实意味着您仍然需要处理该部分。
  • @UlrichEckhardt 正如我所愿,在此示例中运行的附加代码是从我无法访问其源代码的动态链接库中导入的。试图重现它要么理解问题要么在黑暗中拍摄。我宁愿希望有人知道可能导致此问题的原因,这将使它可以重现..
  • 这仍然意味着你的问题是题外话,恐怕,即使这是一个有趣的问题。无论如何,有一件事让我印象深刻:“/opt/rh/devtoolset-9/root/usr/include/c++/9/bits/std_mutex.h”。我不知道那是什么,但最后,您将系统中的 libstdc++ 与它结合使用,这可能是也可能不是一个好的组合。也许,即使您不能在此处发布它,创建一个minimal reproducible example 仍然会有帮助吗?另外,请注意构建环境以避免例如混合不同的 C++ ABI。

标签: c++ multithreading c++11 condition-variable


【解决方案1】:

我也遇到了同样的问题。做了几次实验后发现,如果condition_variable破坏后notify_all开始工作,notify_all就会死锁。

请参阅下面的代码。

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::thread* t;
void test() {
    std::condition_variable cv;
    std::mutex cv_m;
    t = new std::thread([&](){
        std::this_thread::sleep_for(std::chrono::seconds(3));
        std::cout << "...before notify_all
";
        cv.notify_all();
        std::cout << "...after notify_all
";
    });

    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... 
";
    cv.wait(lk, []{return true;});
    std::cout << "...finished waiting
";
}

int main()
{
    test();
    t->join();
}

在 Linux 上:

LSB Version:    :core-4.0-amd64:core-4.0-noarch:graphics-4.0-amd64:graphics-4.0-noarch:printing-4.0-amd64:printing-4.0-noarch
Distributor ID: CentOS
Description:    CentOS release 6.3 (Final)
Release:    6.3
Codename:   Final

名称信息:

Linux xxx_name 3.10.0_3-0-0-34 #1 SMP Sun Apr 26 22:58:21 CST 2020 x86_64 x86_64 x86_64 GNU/Linux

使用 gcc 8.2.0 编译代码:

g++ --std=c++11 test.cpp -o test_cond -lpthread

程序会在输出“...before notify_all”后挂起,永远不会到达“...finished waiting”。

但是,使用 gcc 12.1.0 编译代码程序将成功运行。

【讨论】:

    【解决方案2】:

    在我看来,你应该在调用 notify_all (https://en.cppreference.com/w/cpp/thread/condition_variable) 之前解锁生产者中的互斥量

    【讨论】:

    • 不,这不是必需的。
    • 你绝对不应该。这只会让性能变差。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-21
    • 1970-01-01
    • 2022-06-16
    • 2013-07-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多