使用承诺数组的分段错误

Segmentation fault using array of promises

我编写了多线程程序,使用 promises 和 futures 来检查给定数字是否为素数。这个程序的正确性或效率不是重点,而是每次执行时发生的分段错误的事实——我在更复杂的程序中遇到过类似的问题,所以我写了这个简单的程序来理解它,但错误仍然存​​在这里。也许我不了解并发性,或者 promises/futures,但我认为这里的一切都以正确的方式完成......有人可以解释为什么它不起作用吗?这将非常有帮助:)

代码如下:

#include <future>
#include <thread>
#include <iostream>
#include <initializer_list>
#include <vector>
#include <cassert>


namespace {
const int THREADS_NUMBER = 8;

void f(int n, std::vector<int>& divisiors, std::promise<bool>& isPossiblePrime) {
    bool isPrimeCandidate = true;
    for (auto i : divisiors)
        if (n % i == 0) {
            isPrimeCandidate = false;
            break;
        }
    isPossiblePrime.set_value(isPrimeCandidate);
}


}

int main() {
    int n;
    std::cin >> n;
    assert(n > 2);
    std::promise<bool> promises[THREADS_NUMBER];
    std::future<bool> futures[THREADS_NUMBER];
    for (int i = 0; i < n; i++)
        futures[i] = promises[i].get_future();
    std::thread threads[THREADS_NUMBER];

    std::vector<int> divisiors[THREADS_NUMBER];
    for (int i = 2; i < n; i++)
        divisiors[i % THREADS_NUMBER].push_back(i);

    for (int i = 0; i < THREADS_NUMBER; i++) 
        threads[i] = std::thread{ [&]() { f(n, divisiors[i], promises[i]); }};

    bool isPrime = true;
    for(auto & f : futures) {
        bool out = f.get();
        isPrime = out && isPrime;
    }

    for (auto& t : threads)
        t.join();

    if(isPrime) std::cout << "PRIME" << std::endl;
    else std::cout << "NOT PRIME" << std::endl;

}

我在 Linux 上用 g++ -std=c++11 -Wall -lpthread 编译。

问题是由于这一行:for (int i = 0; i < n; i++) futures[i] = promises[i].get_future(); - 如果 n>THREADS_NUMBER 然后它尝试 read/write from/to promises/futures 未分配。

问题出现在:

for (int i = 0; i < THREADS_NUMBER; i++) 
    threads[i] = std::thread{ [&]() { f(n, divisiors[i], promises[i]); }};

[&] 通过引用捕获所有变量。这包括 promisesi。但是,lambda 中的代码尚未执行。它不会 运行 直到操作系统设置线程等。当线程开始执行时,主线程的 for 循环已经结束,变量 i 结束了它的生命周期。

但随后线程执行 promises[i],其中两者都是引用捕获。为此,它查找 promisesi 的引用。 promises 很好,但是 i 指的是一个不再存在的变量。段错误可能是由于 i 由于此查找而具有垃圾值,然后越界访问。

即使将 int i; 提升到循环之外也无法解决问题;那么 lambda 中的引用确实找到了 i,但它在完成 THREADS_NUMBER 的循环后具有最终值,这超出了 promises 数组的范围。

要解决此问题,请按值捕获 i

threads[i] = std::thread{ [&,i]() { f(n, divisiors[i], promises[i]); }};

然后每个 lambda 使用创建 lambda 时的值 i


注意:我认为 std::thread 可以将临时 lambda 作为构造函数参数,但我对此也不确定。该标准似乎确实说线程构造函数复制了提供的仿函数。但是我搜索的所有 cppreference 示例都首先创建了一个命名的 lambda,然后将该名称作为构造函数参数提供给 std::thread.