提问人:Lukas Lang 提问时间:10/27/2023 最后编辑:Lukas Lang 更新时间:10/29/2023 访问量:41
协程帧被其他协程覆盖?(-O2 及更高版本上的 GCC 11.3)
Coroutine frame overridden by other coroutine? (GCC 11.3 on -O2 and higher)
问:
我在 GCC 11.3 中遇到了我的协程问题:我实现了一个事件循环,其中多个协程交替向前步进(如果它们的 awaitable 再次准备就绪)。我最近注意到优化的构建没有按预期运行,我相信我已经将其缩小到帧以某种方式被其他协程覆盖。
代码如下:(Godbolt 链接)
#include <array>
#include <cstdio>
#include <coroutine>
struct task
{
struct promise_type
{
auto get_return_object() -> task
{
return task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
auto initial_suspend() noexcept -> std::suspend_always { return {}; }
auto final_suspend() noexcept -> std::suspend_always { return {}; }
void return_void() {}
void unhandled_exception() {}
};
task(std::coroutine_handle<promise_type> handle_) : handle{handle_} {}
std::coroutine_handle<promise_type> handle;
};
auto main(void) -> int
{
auto a = "a";
auto b = "b";
std::array tasks = {
[&]() -> task {
printf(a);
co_return;
}(),
[&]() -> task
{
printf(b);
co_return;
}()
};
for (auto& task : tasks)
task.handle.resume();
}
代码说明
代码首先定义了 ,一个基本的协程类型。然后,它创建一个包含两个 (print 和 、 respecitely) 的数组,并恢复它们的协程句柄。task
task
a
b
结果
Godbolt 链接配置了三个编译器:
- GCC 11.3, : 正确打印
-O1
ab
- GCC 11.3, : 打印
-O2
bb
- GCC 12.1:正确打印
-O3
ab
由于 12.1 不再显示该问题,因此看起来有些问题已得到解决。不幸的是,我无法更新我的编译器,所以我试图了解是什么触发了这个问题,以及如何避免它。所以:
发生了什么事情?这是编译器错误吗?如何解决这个问题?
答:
0赞
Lukas Lang
10/29/2023
#1
在进一步减少我的 MWE 并随后改写我的搜索词后,我能够找到问题的答案:错误在 C++ 标准中,而不是 GCC。
发生的事情是,s 的 lambda 是通过引用/指针在协程帧中捕获的(据我所知,这是由标准规定的)。由于协程对象是通过立即调用的 lambda 表达式创建的,因此一旦协程最初挂起(在我的情况下是立即挂起),lambda 对象本身就会超出范围。要解决此问题,我们有两种选择:task
- 确保 lambda 在协程中幸存下来。
- 确保在协程首次挂起之前将 lambda 捕获复制到局部变量中。
第一个选项可以按如下方式实现(Godbolt):
#include <array>
#include <cstdio>
#include <coroutine>
struct task
{
struct promise_type
{
auto get_return_object() -> task
{
return task{std::coroutine_handle<promise_type>::from_promise(*this)};
}
auto initial_suspend() noexcept -> std::suspend_always { return {}; }
auto final_suspend() noexcept -> std::suspend_always { return {}; }
void return_void() {}
void unhandled_exception() {}
};
task(std::coroutine_handle<promise_type> handle_) : handle{handle_} {}
std::coroutine_handle<promise_type> handle;
};
auto main(void) -> int
{
auto a = "a";
auto b = "b";
auto task_a = [&]() -> task {
printf(a);
co_return;
};
auto task_b = [&]() -> task
{
printf(b);
co_return;
};
std::array tasks = {
task_a(),
task_b()
};
for (auto& task : tasks)
task.handle.resume();
}
请注意,在从它们构造协程之前,我只是简单地将 lambda 本身存储在局部变量中。这保证了 lambda 仅在协程被销毁后才会被销毁。(假设协程在函数退出之前完成)
评论