使用 C++ future 作为函数堆栈中的中间值会导致段错误

Using a C++ future as an intermediate value in a function stack results in a segfault

我在理解 C++11 promises、futures 以及它们如何与不同上下文交互时遇到一些问题。

总的来说,我的目标是让一个程序在计算线程中生成值并在主线程中打印它们。在主线程获取生成值之前,我想拦截并更改它。在底部的示例代码中,拦截了值为 asdf 的未来,并在前面添加了 redirect:,将 redirect:asdf 返回给未来。

使用 LLVM 9、GCC 5/6/7 或 Visual C++ 19 编译此代码工作正常。但是,在抛出奇怪错误的同时,所有在 lambda 中的 f.get() 都爆炸了。例如,在 MacOS 上使用 LLVM (LLDB) 进行调试会从 futures 库的某个深处给出 EXC_BAD_ACCESS (code=1, address=0x18),然后退出代码 11(段错误)。我认为这不是库实现的问题,因为它在所有编译器上的行为都相同。

我发现有几种方法可以消除错误,但代码不在我想要的结构中。一种是从 push_redirect 简单地 return f;,丢弃异步内容而不改变 future 的值。另一种是从 main 而不是 push_redirect 调用 push_new,同样不改变未来的价值。归根结底,我希望能够根据需要堆叠尽可能多的未来重定向。

有没有我做的特别不对的地方?我怀疑这可能与 lambda 的按引用捕获有关,但我不知道如何安排代码以避免在不使用全局变量的情况下按引用捕获。这也可能与范围有关。

下面是一个最小的示例,是从出现此错误的较大程序中剥离出来的。它应该在任何可以处理 C++11 或更好的 online 或离线 C++ 编译器上编译。

#include <string>
#include <iostream>
#include <future>
#include <queue>

struct PromiseContainer {
    std::promise<std::string> p;
};

std::queue<PromiseContainer *> q;

void other_thread()
{
    std::string str("abcd");

    while (true) {
        while (q.empty());

        auto pc = q.front();
        q.pop();

        if (pc == nullptr) break;
        else {
            pc->p.set_value(str);
            delete pc;
        }
    }
}

std::future<std::string> push_new()
{
    auto p = std::promise<std::string>();
    auto f = p.get_future();

    auto pc = new PromiseContainer();
    pc->p = std::move(p);
    q.push(pc);

    return f;
}

std::future<std::string> push_redirect()
{
    auto f = push_new();
    return std::async(std::launch::deferred, [&]()->std::string {
        return "redirect:" + f.get();
    });
}

int main()
{
    auto t = std::thread(other_thread);

    auto f = push_redirect();
    q.push((PromiseContainer *) nullptr);

    f.wait();
    std::cout << f.get() << std::endl;

    t.join();
}

f in push_redirect 是局部变量所以你的 lambda(带 &)

[&]()->std::string {
    return "redirect:" + f.get();
});

持有对这个变量的引用,当 push_redirect 结束时 f 被删除并且你得到未定义的行为 - 异步创建的线程想要读取被销毁的数据。

如果您使用的是 C++14,您可以在 lambda 捕获列表中移动 f 未来对象:

std::future<std::string> push_redirect()
{
  auto f = push_new();
  return std::async(std::launch::deferred, [f = std::move(f)]() mutable ->std::string {
    return "redirect:" + f.get();
  });
}

您还应该使用互斥来同步对 q 队列的访问。