C++ 20 协程乱炖

不对 C++ 20 里的协程做过多的基础介绍,别人讲得好多了
仅作为俺的笔记使用,理解可能存在问题,欢迎指正

https://zh.cppreference.com/w/cpp/language/coroutines 圣经

https://zhuanlan.zhihu.com/p/561623494 图不错

https://uint128.com/2022/02/21/%E7%90%86%E8%A7%A3C-20-Coroutine-co-await%E4%B8%8EAwaiter/ 字多

https://sf-zhou.github.io/coroutine/cpp_20_coroutines.html 例子挺好

Notes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// suspend_always
case 7u:
...;
frame_ptr->_Coro_promise.m_value = 12;
frame_ptr->_Coro_resume_fn = 0LL;
frame_ptr->_Coro_resume_index = 8;
return;
case 8u:
operator delete(frame_ptr);

// suspend_never
case 7u:
...;
frame_ptr->_Coro_promise.m_value = 12;
frame_ptr->_Coro_resume_fn = 0LL;
goto LABEL_43;
case 8u:
LABEL_43:
operator delete(frame_ptr);

协程函数的编译器处理实例

基于 g++ 11.3.0 -Og -std=c++20 的编译结果,反编译并极大的简化了逻辑,与实际存在出入

例程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
template<typename T>
struct Task {
struct Promise {
T value;
Task<T> get_return_object() {
return Task<T>{ std::coroutine_handle<Promise>::from_promise(*this) };
}
static std::suspend_always initial_suspend() { return {}; }
static std::suspend_always final_suspend() { return {}; }
std::suspend_always yield_value(T&& v) {
value = v;
return {};
}
void return_value(T&& v) { value = v; }
void unhandled_exception() {}
};
using Handle = std::coroutine_handle<Promise>;
explicit Task(Handle handle_) : handle(handle_) {}
bool await_ready() { return handle.done(); }
T await_resume() {
return std::move(handle.promise().value);
}
void await_suspend(std::coroutine_handle<> handle) {}
Handle handle;
};

Task<int> foo(int a) {
co_return a+114;
}

Task<int> bar() {
int a = 514;
a = co_await foo(a);
printf("foo 1: %d\n", a);
a = co_await foo(1919);
printf("foo 2: %d\n", a);
co_return 810;
}

大概的编译器处理结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
struct _foo_frame {
// something like function body's entry.
FunctionEntry* _function_entry;
FunctionEntry* ...;

// must haves
int _state;
Task<int>::Promise _promise;
std::coroutine_handle<Task<int>::Promise> _self_handle;

// local variables / captures / parameters
int _param_a;

// some other things the implement added
DontKnow ...;
};

Task<int> foo(int a) {
using Handle = std::coroutine_handle<Task<int>::Promise>;
Handle handle;
_foo_frame* frame_ptr = new _foo_frame; // call Promise::new
handle.frame_ptr = frame_ptr;
frame_ptr->_function_entry = (void(*)(_foo_frame&))foo;
frame_ptr->_state = 0;
frame_ptr->_self_handle = handle;
frame_ptr->_param_a = a;
...; // initialization for frame_ptr's other properties

Task<int> task{handle};
return task;
}
void foo(_foo_frame& frame) {
try {
switch(frame._state) {
case 0:
frame._state = 1;
suspend_awalys.await_suspend(frame._self_handle); // co_await initial_suspend
return; // initial_suspend
case 1:
frame._promise.return_value(frame._param_a+114); // co_return a+114;
frame._state = 2;
[[ fallthrough ]]
case 2:
frame._state = 3;
suspend_awalys.await_suspend(frame._self_handle); // co_await final_suspend
return; // final_suspend, coro is done here!
case 3: // should never resume here
delete &frame; // clean up
return;
default:
BUG(); // something went wrong...
}
} catch(...) {
frame._promise.unhandled_exception();
frame._state = 2;
return;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
struct _bar_frame {
// something like function body's entry.
FunctionEntry* _function_entry;
FunctionEntry* ...;

// must haves
int _state;
Task<int>::Promise _promise;
std::coroutine_handle<Task<int>::Promise> _self_handle;

// local variables / captures / parameters / temp variable that need to cross state
int _local_a;
Task<int> _temp_foo_1;
Task<int> _temp_foo_2;

// some other things the implement added
DontKnow ...;
};

Task<int> bar() {
using Handle = std::coroutine_handle<Task<int>::Promise>;
Handle handle;
_bar_frame* frame_ptr = new _bar_frame; // call Promise::new
handle.frame_ptr = frame_ptr;
frame_ptr->_function_entry = (void(*)(_bar_frame&))bar;
frame_ptr->_state = 0;
frame_ptr->_self_handle = handle;
...; // initialization for frame_ptr's other properties

Task<int> task{handle};
return task;
}
void bar(_bar_frame& frame) {
try {
switch(frame._state) {
case 0:
frame._state = 1;
suspend_awalys.await_suspend(frame._self_handle); // co_await initial_suspend
return; // initial_suspend
case 1:
frame._local_a = 514; // int a = 514;
frame._temp_foo_1 = foo(frame._local_a); // foo(a);
frame._state = 2;
frame._temp_foo_1.await_suspend(frame._self_handle); // co_await
return; // state is divided by co_await
case 2:
frame._local_a = frame._temp_foo_1.await_resume(); // a = [await_result];
printf("foo 1: %d\n", frame._local_a); // printf("foo 1: %d\n", a);
frame._temp_foo_2 = foo(1919); // foo(1919);
frame._state = 3;
frame._temp_foo_2.await_suspend(frame._self_handle); // co_await
return; // state is divided by co_await
case 3:
frame._local_a = frame._temp_foo_2.await_resume(); // a = [await_result];
printf("foo 2: %d\n", frame._local_a); // printf("foo 2: %d\n", a);
frame._promise.return_value(frame._local_a); // co_return a;
frame._state = 4;
[[ fallthrough ]]
case 4:
frame._state = 5;
suspend_awalys.await_suspend(frame._self_handle); // co_await final_suspend
return; // final_suspend, coro is done here!
case 5: // should never resume here
delete &frame; // clean up
return;
default:
BUG(); // something went wrong...
}
} catch(...) {
frame._promise.unhandled_exception();
frame._state = 4;
return;
}
}

总结

C++ 标准委员会纯纯的老逼登

  1. 你妈的 async 标识符不用多搞出来这些 co_xxxx,async 后用 return 和 yeild 不好吗?
  2. 你这玩意确实定制性够强,但能不能先给个能用的运行时,看完这一套下来人都快麻了,别说写了,另外完全没配套,网络库 C++ 23 了都没见着
  3. 那个 promise 的构造函数坑更显老逼登风范,完全不知道实际应用价值
  4. 隔壁 rust 通过生命周期都给协程动态内存分配消灭玩明白了,还在这吵吵采用哪个方案来确定协程帧大小比较好,连个方便 workaround 走的路都没有
正在回复第 0-0
0条评论 使用邮件回复 手动发送 匿名