c++ - 在协程调用 `Segmentation fault` 之后,是什么导致此代码 `promise_type::return_value()` ?

标签 c++ promise segmentation-fault coroutine c++20

已更新

Segmentation fault是由尝试引起的 .get()在无效 std::future .然而协程仍然不能正常工作。


[ godbolt ]
[ wandbox ]

我有这样的等待类型:

template<typename T>
struct awaitable
{
  awaitable() = default;
  awaitable(awaitable&& other) : val(std::move(other.val) ) { }
  awaitable(std::future<T>&& other_f) : val(std::move(other_f ) ) { }
  bool await_ready() { return true; }
  void await_suspend(std::experimental::coroutine_handle<> h) { h.resume(); }
  T await_resume() { return val.get(); }
private:
  std::future<T> val;
};

像这样专门化 coroutine_traits 的 promise_type

template<typename T, typename ...Args>
struct std::experimental::coroutine_traits<awaitable<T>, Args...>
{
  struct promise_type {
    suspend_never  initial_suspend()      { return {}; }
    suspend_never  final_suspend()        { return {}; }
    void           unhandled_exception()  { std::terminate(); }
    awaitable<T>   get_return_object()    { return std::move(f); }
    T              return_value(T r)      { 
      if constexpr (_DEBUG) std::cout << "About to return_value(" << r << ')' << std::endl;
      return r; }
  private:
    awaitable<T>   f;
  };
};

有一些服务员

awaitable<int> async_add(int a, int b) {
  auto fut = std::async([=]() {
    int c = a + b;
    return c;
  });

  return std::move(fut);
}

awaitable<int> async_fib(int n)
{
  if (n <= 2)
      co_return 1;

  int a = 1, b = 1;

  // iterate computing fib(n)
  for (int i = 0; i < n - 2; ++i)
  {
    int c = co_await async_add(a, b);
    if constexpr (_DEBUG) {
      std::cout << 
        "After co_await async_add(" << std::setw(3) << a << 
        ","    << std::setw(3) << b << ")\t"
        "i = " << i << "\t"
        "c = " << c << "\t"
        "[ fib(" << i + 3 << ") ]" << std::endl;
    }
    a = b;
    b = c;
  }

  co_return b;
}

当我尝试检索值时:

int main()
{
  std::string str;
  std::getline(std::cin, str);
  awaitable<int> continua_v = async_fib(std::stoi(str) );
  if constexpr (_DEBUG) std::cout << "About to retrieve value..." << std::endl;
  int result = continua_v.await_resume();
  if constexpr (_DEBUG) std::cout << "Retrieving has succeeded : " << result << std::endl;
  return result;

}

出现段错误

Start

After co_await async_add(  1,  1)   i = 0   c = 2   [ fib(3) ]
After co_await async_add(  1,  2)   i = 1   c = 3   [ fib(4) ]
After co_await async_add(  2,  3)   i = 2   c = 5   [ fib(5) ]
After co_await async_add(  3,  5)   i = 3   c = 8   [ fib(6) ]
After co_await async_add(  5,  8)   i = 4   c = 13  [ fib(7) ]
After co_await async_add(  8, 13)   i = 5   c = 21  [ fib(8) ]
After co_await async_add( 13, 21)   i = 6   c = 34  [ fib(9) ]
After co_await async_add( 21, 34)   i = 7   c = 55  [ fib(10) ]
About to return_value(55)
About to retrieve value...

Segmentation fault

怎么取值的时候崩溃了?
我该如何解决这个问题?

[ godbolt ]
[ wandbox ]


已更新

Segmentation fault是由尝试引起的 .get()在无效 std::future .这可以通过预检查来衡量。

  T await_resume() { 
    if (val.valid()) {
      if constexpr (_DEBUG) std::cout << "About to val.get()..." << std::endl;
       t_val = val.get();
      if constexpr (_DEBUG) std::cout << "val.get() : " << t_val << std::endl;
    }
    return t_val; }

可惜好像上线了

awaitable<int> continua_v = async_fib(std::stoi(str) );

continua_v不知何故总是得到默认值 awaitable<int>这是不可取的。

Start

About to val.get()...
val.get() : 2
After co_await async_add(  1,  1)   i = 0   c = 2   [ fib(3) ]
About to val.get()...
val.get() : 3
After co_await async_add(  1,  2)   i = 1   c = 3   [ fib(4) ]
About to val.get()...
val.get() : 5
After co_await async_add(  2,  3)   i = 2   c = 5   [ fib(5) ]
About to val.get()...
val.get() : 8
After co_await async_add(  3,  5)   i = 3   c = 8   [ fib(6) ]
About to val.get()...
val.get() : 13
After co_await async_add(  5,  8)   i = 4   c = 13  [ fib(7) ]
About to val.get()...
val.get() : 21
After co_await async_add(  8, 13)   i = 5   c = 21  [ fib(8) ]
About to val.get()...
val.get() : 34
After co_await async_add( 13, 21)   i = 6   c = 34  [ fib(9) ]
About to val.get()...
val.get() : 55
After co_await async_add( 21, 34)   i = 7   c = 55  [ fib(10) ]
About to return_value(55)
About to retrieve value...
Retrieving has succeeded : -1

255

[ godbolt ]
[ wandbox ]

最佳答案

我认为对这个问题缺乏兴趣主要是因为协程太新了(此时甚至在技术上还没有标准化)。不过,这是一个有趣的话题。我不得不做一些挖掘,但我发现了 this talk from CppCon 2016非常有帮助,还有这个 draft of the coroutine standard .

直面问题:我认为您(和我在做一些研究之前)对应该如何编写协程有点困惑,不幸的是,这需要对 C++ 语言如何实现它们有很多了解在引擎盖下。例如,协程必须通过句柄控制(包括恢复),但您并未将句柄存储在任何地方。

此外,您可能不应该使用 std::futurestd::async(仅供引用使用 std::async没有 std::launch::async 几乎没用)。这些类型很难单独使用,也不利于在 C++20 中编写协程。例如,在我的测试中,我发现段错误是由 std::future 实例在访问点无效引起的。

这是您提供的固定代码(去除了不必要的细节)。它对所有重要内容都有评论,说明发生了什么事、发生了什么事以及为什么发生。我选择将所有内容直接嵌套在 awaitable 中,这样更清晰易读,但也可以通过 coroutine_traits 完成。

注意:我在 VisualStudio 2017 中使用最新的标准草案和 /await 编译标志编写/测试了它。

#include <iostream>
#include <utility>
#include <experimental/coroutine>

template<typename T>
class awaitable
{
public: // -- promise type -- //

    // declare the promise type and alias the handle type
    struct promise_type;
    typedef std::experimental::coroutine_handle<promise_type> handle;

    struct promise_type
    {
        T ret_val; // storage location for the return value

        // we need to provide a way to convert a promise into the awaitable object (via the handle)
        awaitable get_return_object() { return awaitable { handle::from_promise(*this) }; }

        // we won't suspend upon starting - we want to start immediately
        auto initial_suspend() { return std::experimental::suspend_never{}; }
        // we need to buffer the return value, so we need to suspend at the end of the coroutine.
        // if we didn't do this the coroutine object could be destroyed and we'd access undefined memory.
        auto final_suspend() { return std::experimental::suspend_always{}; }

        // this is called when the coroutine body uses co_return expr
        // where expr is not cv-qualified void (otherwise return_void() is used).
        // the current coroutine standard draft dictates this MUST return void.
        template<typename U>
        void return_value(U &&v) { ret_val = std::forward<U>(v); }

        // this is called if an exception is thrown in the coroutine.
        // immediately after this, the final suspend is performed (so the coroutine will be done() after this).
        void unhandled_exception()
        {
            // we can either terminate() (i.e. we do not allow/expect exeptions)
            // or we could store and rethrow the exception later (i.e. std::exception_ptr).
            // i'll use the terminate() option since that's what you used.
            std::terminate();
        }
    };

public: // -- non-coroutine support -- //

    // we provide a method to get the value - note that this method is not a coroutine.
    // if we didn't provide this, we couldn't use it in e.g. main() because main() cannot be a coroutine.
    T get()
    {
        while (!co.done()) co.resume(); // resume the coroutine until it's done (at final suspend)
        return co.promise().ret_val;    // then get its value (via copy to allow calling get() multiple times)
    }

public: // -- coroutine support -- //

    bool await_ready() { return co.done(); } // are we already done?
    T    await_resume() { return get(); }    // when await is resumed, we need to block and get the value

    // no special suspend logic - defaults are good.
    // this takes generic coroutine_handle<> rather than handle to be generic.
    // coroutine_handle<> is the same as coroutine_handle<void>.
    // it can kind of be though of like a void* to a coroutine of any type.
    // i.e. we can suspend this coroutine to co_await a coroutine of any type.
    void await_suspend(std::experimental::coroutine_handle<>) {}

public: // -- data -- //

    // we need to store a handle for the coroutine
    handle co = nullptr;

public: // -- ctor / dtor / asgn -- //

    // we can make an awaitable type from a (coroutine) handle
    explicit awaitable(handle h) : co(h) {}

    // in dtor, we need to destroy the coroutine if it's still valid
    ~awaitable() { if (co) co.destroy(); }

    // we can also make an empty awaitable
    awaitable() = default;

    // we can't copy an awaitable
    awaitable(const awaitable&) = delete;
    awaitable &operator=(const awaitable&) = delete;

    // but we can move from one
    awaitable(awaitable &&other) : co(std::exchange(other.co, nullptr)) {}
    awaitable &operator=(awaitable &&other) { co = std::exchange(other.co, nullptr); return *this; }
};

awaitable<int> async_add(int a, int b) { co_return a + b; }

awaitable<int> async_fib(int n)
{
    if (n <= 2) co_return 1;

    int a = 1, b = 1;

    for (int i = 0; i < n - 2; ++i)
    {
        int c = co_await async_add(a, b); // we're a coroutine, so we can co_await the result of async_add
        a = b;
        b = c;
    }

    co_return b;
}

int main()
{
    // we're NOT a coroutine, so we can't suspend and do other things - use .get() rather than co_await
    std::cerr << async_fib(8).get() << '\n';

    return 0;
}

关于c++ - 在协程调用 `Segmentation fault` 之后,是什么导致此代码 `promise_type::return_value()` ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56211606/

相关文章:

c++ - 当值改变时,获取 QComboBox 的先前值,该值位于 QTableWidget 中

c++ - 在 Boost Asio 缓冲区中搜索

c++ - 使用动态链接在 Boost 1.60 中使用 boost::coroutine2 构建 fibonnacci 示例的链接器错误

javascript - 在映射函数中的 .then 内部分配变量

c - C 函数调用时出现段错误

linux - 无法使用curl从Linux服务器上的firebase存储下载文件会出现段错误

c++ - 为什么我的 LLVM JIT 实现会出现段错误?

c++ - 多点运算符(c++类)

javascript - NodeJS Windows 执行 promise 待定

javascript - Promise.all([]) 返回已解决的 Promise,但 Promise.race([]) 返回待处理的 Promise。为什么它们不同?