【问题标题】:ASIO strand blocked after co_spawn is co_await-ed在 co_spawn 被 co_await-ed 后,ASIO 链被阻塞
【发布时间】:2020-10-26 20:15:27
【问题描述】:

我一直在尝试使用 ASIO strands 和 C++20 协程,并注意到奇怪的行为。当我在一个链上生成一个协程,并在其中在第二个链上生成另一个协程并等待它时,当控制权返回到外部协程时,两个链都被阻塞。这是预期的行为吗?如果是这样,我该如何解决这个问题?

以下代码演示了该问题,在 linux 上使用 boost.asio、boost 版本 1.74.0、gcc 版本 10.2.0 进行了测试。

#include <thread>
#include <chrono>
#include <iostream>

#include <boost/asio.hpp>

namespace asio = boost::asio;
using namespace std::chrono_literals;

int main() {
    auto ioc = asio::io_context{};
    // Create two strands
    auto s1 = asio::make_strand(ioc);
    auto s2 = asio::make_strand(ioc);
    // Prevent the io context from stopping
    auto wg = asio::make_work_guard(ioc);
    // Over-provision threads just in case
    auto w1 = std::jthread{[&]() { ioc.run(); }};
    auto w2 = std::jthread{[&]() { ioc.run(); }};
    auto w3 = std::jthread{[&]() { ioc.run(); }};
    auto w4 = std::jthread{[&]() { ioc.run(); }};
    auto w5 = std::jthread{[&]() { ioc.run(); }};

    asio::co_spawn(s1, [&]() -> asio::awaitable<void> {
        std::cout << "a" << std::endl;
        co_await asio::co_spawn(s2, []() -> asio::awaitable<void> {
            // Works as intended
            // s2 is blocked, s1 is free (b will print)
            std::this_thread::sleep_for(2s);
            co_return;
        }, asio::use_awaitable);
        // Expected: s1 is blocked, s2 is free (d will print, e will wait 5s)
        // Reality: both s1 and s2 are blocked (after 5s, d and e print at the same time).
        // Why?
        std::cout << "c" << std::endl;
        std::this_thread::sleep_for(5s);
    }, asio::detached);

    std::this_thread::sleep_for(1s);

    asio::co_spawn(s1, []() -> asio::awaitable<void> {
        std::cout << "b" << std::endl;
        co_return;
    }, asio::detached);

    std::this_thread::sleep_for(2s);

    asio::co_spawn(s2, []() -> asio::awaitable<void> {
        std::cout << "d" << std::endl;
        co_return;
    }, asio::detached);

    asio::co_spawn(s1, []() -> asio::awaitable<void> {
        std::cout << "e" << std::endl;
        co_return;
    }, asio::detached);

    std::this_thread::sleep_for(10s);
    ioc.stop();
}

预期行为:

t__0_1_2_3_4_5_6_7
s1 a_b_c_________e
s2 ______d________

实际行为:

t__0_1_2_3_4_5_6_7
s1 a_b_c_________e
s2 ______________d

【问题讨论】:

  • 正确的思维方式是:在协程内链;不是相反。

标签: c++ boost-asio c++20 c++-coroutine


【解决方案1】:

想出了一个解决方案:不是在链上生成协程,而是在 io 上下文中生成它,并在需要通过该链访问时发布到链。

asio::co_spawn(ioc, [&]() -> asio::awaitable<void> {
        co_await asio::post(s1, asio::use_awaitable);

        std::cout << "a" << std::endl;

        co_await asio::post(s2, asio::use_awaitable);

        std::this_thread::sleep_for(2s);

        co_await asio::post(s1, asio::use_awaitable);

        std::cout << "c" << std::endl;
        std::this_thread::sleep_for(5s);
    }, asio::detached);

【讨论】:

  • 我不相信这个答案是正确的。您的代码不再在链中执行。您可以通过添加assert(s1.running_in_this_thread());来确认
【解决方案2】:

boost-asio 模型在 stateful coroutine [docs] 中生成一个链。因此,您的代码的行为正是它应该如何工作(即,以同步方式)。

对于stateless 协程,您的假设可能是正确的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多