【问题标题】:deadlock on c++ std::thread even with mutex lock即使使用互斥锁,C++ std::thread 也会出现死锁
【发布时间】:2017-09-06 12:40:17
【问题描述】:

我试图更好地理解 C++ 线程。我实现了一个小例子,我希望四个线程​​在 std::vector 内的索引区域上工作。

当然,我遇到了死锁并阅读了互斥锁和锁的使用。据我了解,一般假设是所有变量都由线程共享,除非另有明确说明(thread_local)。如果一个线程更改了任何全局数据,明智的做法是先锁定资源,对数据进行处理以避免数据竞争,然后再次解锁数据,以便其他线程可以使用它。

在我的示例中,std::cout 上的锁工作正常,线程创建正常,函数调用正常,但即使我在处理数据之前和之后实现了 data_lock,程序仍然挂起。 如果我注释掉数据操作并显示一条消息,它也可以正常工作。每次运行的输出都不一样,所以我不要发布它。

我的感觉是我错过了一些我不知道的 C++ 线程的概念(我之前使用过 MPI)。

我的问题:

  1. 是否有我遗漏的概念?我还需要了解/阅读哪些内容?
  2. 除了使用互斥锁、锁和 thread_local 来正确执行之外,还有哪些工具?

编译说明:

g++ -std=c++1y -O0 -g3 -Wall -c -fmessage-length=0 -pthread -MMD -MP -MF"src/main.d" -MT"src/main.d" - o "src/main.o" "../src/main.cpp"

代码:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
using namespace std;

std::mutex data_lock;
std::mutex cout_lock;

void output(std::string message){
    cout_lock.lock();
    cout << message << endl;
    cout_lock.unlock();
}

void work(std::vector<double>& data, const int s_ind, const int e_ind)     {
    thread_local int i = 0;
    for (i = s_ind; i <= e_ind; i++) {
        data_lock.lock();
        data[i] = 1.0;
        data_lock.unlock();
        //msg("work");
    }
}

int main() {
    const int size = 1000;
    const int cpus = 4;
    const int chunksize = size / cpus;

    //create Data vector
    std::vector<double> dat { (size) };

    //thread vector
    std::vector<std::thread> threads;

    //create and start threads with proper ranges (ranges tested)
    for (int cpu = 0; cpu < cpus; cpu++) {
        threads.push_back(std::thread(work, ref(dat), (cpu * chunksize),(((cpu + 1) * chunksize) - 1)));
        output("thread created");
    }

    //delete threads
    for (int cpu = 0; cpu < cpus; cpu++) {
        threads[cpu].join();
        output("thread joined");
    }
return 0;
}

【问题讨论】:

  • 一般来说,“即使”你使用互斥锁,你也不会陷入死锁,但是你会遇到死锁,“因为”你使用了互斥锁。如果你有一个死锁,我不确定
  • std::vector&lt;double&gt; dat { (size) }; 是一个大小为 1 的向量,具有单个元素 size。修复后,它可以正常工作here,尽管这并不能证明它很好
  • IIRC 你不需要锁定访问向量的不同元素
  • 除了上述问题之外, 没有包含 - 如果您能够毫无问题地编译此示例,则您的警告级别不够。我认为互斥锁的使用没有任何问题 - 但我认为没有理由使用 thread_local 变量 - 常规的本地 var 就足够了。
  • 非常感谢@Passer。数组实例化似乎是个问题。

标签: c++ multithreading locking mutex deadlock


【解决方案1】:

我知道有人建议在 ctor 中使用{},它消除了隐式函数声明的问题。但是,如果该类具有以std::initializer_list&lt;T&gt; 作为参数而std::vector 的构造函数重载,则会出现问题。所以这一行:

std::vector<double> dat { (size) };

创建一个包含一个元素 size 的向量。

正如您所说的遇到死锁并且必须使用互斥锁,这几乎是不可能的。很可能您的程序挂起,但这不被视为死锁。死锁是指您的线程相互阻塞,试图以错误的顺序(或类似情况)锁定互斥锁。

注意:这不是修复,但您应该使用 RAII 进行互斥锁,标准库为此提供了工具:std::lock_guardstd::unique_lockstd::scoped_lock,如果您有 c++17。原因,在这种情况下(以及许多其他情况)应使用 RAII 的原因描述为 here

RAII 保证资源可用于任何功能 可以访问对象(资源可用性是类不变量, 消除多余的运行时测试)。它还保证所有 资源在其控制对象的生命周期内被释放 结束,以相反的获取顺序。同样,如果资源 获取失败(构造函数异常退出),全部 每个完全建成的成员和基地获得的资源 子对象以初始化的相反顺序释放。这 利用核心语言特性(对象生命周期、范围退出、 初始化和堆栈展开的顺序)以消除资源 泄漏并保证异常安全。这种技术的另一个名字 是 Scope-Bound Resource Management (SBRM),在基本用例之后 RAII 对象的生命周期因范围退出而结束。

【讨论】:

  • 谢谢!我在谷歌搜索中看到了 lock_gurad 和 unique_lock,但我无法真正理解为什么应该使用它们。你能详细说明一下吗?
猜你喜欢
  • 1970-01-01
  • 2015-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多