Boost asio C++ 20 协程:co_spawn 具有引用参数意外结果的协程

R.J*_*R.J 3 c++ boost-asio c++20 c++-coroutine

在下面的代码中,会话协程的参数是通过引用传递的。

#include <boost/asio.hpp>
#include <iostream>

boost::asio::awaitable<void> session(const std::string& name)
{
    std::cout << "Starting " << name << std::endl;
    auto executor = co_await boost::asio::this_coro::executor;
}

int main()
{
    boost::asio::io_context io_context;

    co_spawn(io_context, session("ServerA"), boost::asio::detached);
    co_spawn(io_context, session("ServerB"), boost::asio::detached);

    io_context.run();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

由于某种我不明白的原因,上面的代码会导致打印Starting ServerB两次。

> g++ -std=c++20 ../test-coro.cpp -o test-coro && ./test-coro
Starting ServerB
Starting ServerB

Run Code Online (Sandbox Code Playgroud)

但是当我将协程参数更改为按值传递时,它将正确打印Starting ServerAGetting ServerB

#include <boost/asio.hpp>
#include <iostream>

boost::asio::awaitable<void> session(std::string name)
{
    std::cout << "Starting " << name << std::endl;
    auto executor = co_await boost::asio::this_coro::executor;
}

int main()
{
    boost::asio::io_context io_context;

    co_spawn(io_context, session("ServerA"), boost::asio::detached);
    co_spawn(io_context, session("ServerB"), boost::asio::detached);

    io_context.run();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)
> g++ -std=c++20 ../test-coro.cpp -o test-coro && ./test-coro
Starting ServerA
Starting ServerB
Run Code Online (Sandbox Code Playgroud)

这是预期的还是这是编译器/库错误?如果这是预期的,那么其原因是什么?

环境:
Arch Linux 5.18.16-arch1-1
gcc(GCC)12.2.0
boost版本1.79

seh*_*ehe 7

您可以将协程状态视为包含函数调用堆栈上的内容(这使得函数可恢复):cppreference

当协程开始执行时,它会执行以下操作:

  • 使用运算符 new 分配协程状态对象(见下文)
  • 将所有函数参数复制到协程状态:按值参数被移动或复制,按引用参数保留引用(因此如果在引用对象的生命周期结束后恢复协程,则可能会变为悬空)

协程逻辑上存储对临时字符串的引用。哎呀。

我还没有检查过,但我假设 Asio 的可等待实现以初始值开始suspend_always(这对我来说对于执行器模型来说很直观)。

是的,这意味着co_spawn任何引用参数都意味着必须保证所引用对象的生命周期。

在我的系统上,输出只是

Starting
Starting
Run Code Online (Sandbox Code Playgroud)

一种修复方法就是您所展示的。为了说明生命周期方面:

{
    std::string a="ServerA", b="ServerB";
    co_spawn(io_context, session(a), boost::asio::detached);
    co_spawn(io_context, session(b), boost::asio::detached);

    io_context.run();
}
Run Code Online (Sandbox Code Playgroud)

也是一个有效的修复。对于这个简单的例子,我建议std::string_view无论如何:

住在科里鲁

#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>

boost::asio::awaitable<void> session(std::string_view name)
{
    std::cout << "Starting " << std::quoted(name) << std::endl;
    auto executor = co_await boost::asio::this_coro::executor;
}

int main()
{
    boost::asio::io_context io_context;

    std::string const a="ServerA";
    co_spawn(io_context, session(a), boost::asio::detached);
    co_spawn(io_context, session("ServerB"), boost::asio::detached);

    io_context.run();
}
Run Code Online (Sandbox Code Playgroud)

印刷

Starting "ServerA"
Starting "ServerB"
Run Code Online (Sandbox Code Playgroud)