线程的 C++11 vector 参数似乎未初始化

标签 c++ multithreading c++11

我正在尝试通过共享状态的含义为线程间通信创建概念证明:主线程创建工作线程,通过引用为每个工作线程提供一个单独的 vector ,让每个工作线程完成其工作并用结果填充其 vector ,并且最终收集结果。

但是,奇怪的事情正在发生,除了 vector 初始化和工作线程启动之间的某种竞争之外,我找不到其他解释。这是代码。

#include <iostream>
#include <vector>
#include <thread>


class Case {
public:
    int val;
    Case(int i):val(i) {}
};

void
run_thread (std::vector<Case*> &case_list, int idx)
{
    std::cout << "size in thread " << idx <<": " << case_list.size() << '\n';
    for (int i=0; i<10; i++) {
        case_list.push_back(new Case(i));
    }
}

int
main(int argc, char **argv)
{
    int nthrd = 3;
    std::vector<std::thread> threads;
    std::vector<std::vector<Case*>> case_lists;

    for (int i=0; i<nthrd; i++) {
        case_lists.push_back(std::vector<Case*>());
        std::cout << "size of " << i << " in main:" << case_lists[i].size() << '\n';
        threads.push_back( std::thread( run_thread, std::ref(case_lists[i]), i) );
    }

    std::cout << "All threads lauched.\n";

    for (int i=0; i<nthrd; i++) {
        threads[i].join();
        for (const auto cp:case_lists[i]) {
            std::cout << cp->val << '\n';
        }
    }
    return 0;
}

测试于 repl.it (gcc 4.6.3),程序给出如下结果:

size of 0 in main:0
size of 1 in main:0
size of 2 in main:0
All threads lauched.
size in thread 0: 18446744073705569740
size in thread 2: 0
size in thread 1: 0
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc
exit status -1 

在我的电脑上,除了上面类似的东西,我还得到:

Segmentation fault (core dumped)

看起来线程 0 正在获取一个尚未初始化的 vector ,尽管该 vector 在 main 中似乎已正确初始化。

为了隔离问题,我尝试通过更改行来实现单线程:

threads.push_back( std::thread( run_thread, std::ref(case_lists[i]), i) );

run_thread(case_lists[i], i);

并注释掉:

threads[i].join();

现在程序按预期运行,“线程”在主程序收集结果之前一个接一个地运行。

我的问题是:上面的多线程版本有什么问题?

最佳答案

只要 vector 的容量发生变化,vector 的引用(和迭代器)就会失效。过度分配的确切规则因实现而异,但可能性是,在第一个 push_back 和最后一个之间至少有一个容量变化,并且在最终容量增加之前所做的所有引用都是垃圾它发生的那一刻,调用未定义的行为。

要么 reserve 你的总 vector 大小预先(所以 push_back 不会导致容量增加),初始化整个 vector 到前面的最终大小(因此根本不会发生调整大小),或者完全填充一个循环,然后 启动线程(因此所有调整大小都发生在您提取任何引用之前)。这里最简单的修复方法是将其初始化为最终大小,更改:

std::vector<std::vector<Case*>> case_lists;

for (int i=0; i<nthrd; i++) {
    case_lists.push_back(std::vector<Case*>());
    std::cout << "size of " << i << " in main:" << case_lists[i].size() << '\n';
    threads.push_back( std::thread( run_thread, std::ref(case_lists[i]), i) );
}

到:

std::vector<std::vector<Case*>> case_lists(nthrd);  // Default initialize nthrd elements up front

for (int i=0; i<nthrd; i++) {
    // No push_back needed
    std::cout << "size of " << i << " in main:" << case_lists[i].size() << '\n';
    threads.push_back( std::thread( run_thread, std::ref(case_lists[i]), i) );
}

您可能认为 vector 会相当激进地过度分配,但至少在许多流行的编译器上,情况并非如此;两者 gccclang遵循严格的加倍模式,因此前三个插入每次都会重新分配(容量从 1、到 2、到 4);对第一个元素的引用因插入第二个元素而失效,对第二个元素的引用因插入第三个元素而失效。

关于线程的 C++11 vector 参数似乎未初始化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53200434/

相关文章:

c++ - 将 klib 的 knetfile.c|h 移植到 Windows,当我在 "windows unistd.h"中子时,我得到错误 C2036 : 'void *' : unknown size

c++ - 将相同的键插入 std::map 时抛出异常

c# - 后台线程的 Toast 通知失败

c# - 如何知道线程池中的线程挂起/卡住

java - 现代 Java 并发

c++ - 在 C++ 中创建线程池的最简单方法?

c++ - 以编程方式在 Linux 上更改墙纸

c++ - 我怎样才能执行这个特定的 C++ 类模板重载过程?

c++ - 如何将 vector 添加到我的结构中以创建一个库存系统,在该系统中我可以仅使用一个结构将多种不同的 Wine 添加到系统中?

c++ - 如何让 for_each 使用模板?