更新
Segmentation fault
是由于在无效的std::future
上尝试.get()
引起的。但是,Coroutines仍然还没有起作用。
[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
是由于在无效的std::future
上尝试.get()
引起的。这可以通过预检查。
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]
我认为对这个问题缺乏兴趣主要是因为Coroutines是如此新(甚至在这一点上甚至在技术上都不是标准化(。这是一个有趣的话题。我不得不做一些挖掘,但是我发现CPPCON 2016的这次演讲非常有用,以及Coroutine标准的草稿。
直接陷入问题:我认为您(在进行一些研究之前我也是我也很困惑,对应该如何编写Coroutines,这不幸的是,这需要对C 语言如何实现它们有很多了解在引擎盖下。例如,必须通过手柄来控制Coroutines(包括恢复(,但是您不会在任何地方存储手柄。
此外,您可能不应该使用std::future
或std::async
(并且仅使用std::launch::async
使用std::async
的FYI几乎没有用(。这些类型很难独自使用,并且不利于在C 20中编写Coroutines。例如,在我的测试中,我发现segfault是由 std::future
实例在访问点没有有效的情况下引起的。
这是您提供的固定代码(删除了不必要的详细信息(。它对所有重要的内容都有评论,可以说在哪里以及为什么会发生什么。我选择将所有内容直接嵌套在awaitable
内,以更清洁且易于阅读,但也可以通过coroutine_traits
进行。
注意:我用最新标准草稿和/await
编译标志在VisualStudio 2017中编写/测试了这一点。
#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;
}