c++20 协程与 std::mutex 是否有任何规范

问题描述 投票:0回答:1

C++20 协程对带有

std::mutex
的协程有规范吗?在协程中使用
std::mutex
有问题吗?如果是的话有什么解决办法吗?

以下是3个例子(我对协程还很陌生,如果我错了请纠正我)。

示例 1:带有 std::mutex 和阻塞操作的协程

在协程中,我们需要用锁来执行阻塞操作,这样就不会出现多个协程同时执行这项工作。

task<> with_mutex_and_blocking_op() {
    Result result;
    {
        std::lock_guard<std::mutex> lock(some_non_recursive_mutex);  // ensure there's only one coroutine calls `call_blocking_io`

        result = call_blocking_io();   // block here until IO finishes
    }

    co_await some_awaiter(result);
}

示例 2:带有 std::mutex 的协程

我们可以在

await_suspend
中启动异步操作,并在
await_resume
中获取其结果。异步操作将任务提交到任务队列,队列受到
std::mutex
的保护,或者为异步操作获取一些受锁保护的资源。

void await_suspend(std::coroutine_handle<> handle) {
    auto task = create_an_async_task();

    std::lock_guard<std::mutex> lock(some_non_recursive_mutex);

    task_queue.push(std::move(task));
}

示例 3:使用
std::mutex
保护 co_await 的协程

这个示例1有类似的问题,只不过临界区调用了

co_await

一些想法

示例 1 似乎是一种反模式,即在协程中调用阻塞函数,但我想知道会发生什么?假设协程 A 获取互斥体并调用

call_blocking_io
。由于它是阻塞的,我认为调度程序将切换到另一个协程,例如不幸的是,B 与协程 A 调度在同一线程上,并尝试锁定互斥体。这会导致同一线程尝试两次锁定非递归互斥锁,并导致其未定义的行为。

示例 2 尝试将互斥锁锁定在

await_suspend
中。如果无法获取锁,就会阻塞。在这种情况下,调度程序是否会切换到另一个协程?如果要切换,我认为它与示例1有相同的问题。

示例3已经有了答案和解决方案。我想知道,4年过去了,情况是否仍然如此? C++ 20 是否发布了一些功能来解决这个问题?

c++ c++20 coroutine c++-coroutine
1个回答
0
投票

经典和异步/协程编码风格确实不能很好地互操作。
除了切换到异步本机锁和原语之外,在这些不同的世界边界上还采取了一些折衷措施:

1:将同步和异步代码分离为完全独立的函数,使用各自的语义而不混合:

task<> with_mutex_and_blocking_op() {
    Result result;
    {
        std::lock_guard<std::mutex> lock(some_non_recursive_mutex);  // ensure there's only one coroutine calls `call_blocking_io`

        result = call_blocking_io();   // block here until IO finishes
    }

    return []() { 
      //notice the async part separated from normal sync
      co_await some_awaiter(result)
    } ();
}

2:使用try_lock(timeout)并回退到异步co_yield,以便父级决定下一步
(顺便说一句,原始的 Q 代码在任何方面都不是异步/协程的,并且非常好 - 但如果需要像 1 中那样进行修改,它就会成为异步/协程:)。 )

void await_suspend(std::coroutine_handle<> handle) {
    auto task = create_an_async_task();

    while (!some_mutex.try_lock(timeout))
      co_await suspend();
    //std::lock_guard<std::mutex> lock(some_non_recursive_mutex);

    task_queue.push(std::move(task));
    some_mutex.unlock();
}
© www.soinside.com 2019 - 2024. All rights reserved.