C++20 协程对带有
std::mutex
的协程有规范吗?在协程中使用std::mutex
有问题吗?如果是的话有什么解决办法吗?
以下是3个例子(我对协程还很陌生,如果我错了请纠正我)。
在协程中,我们需要用锁来执行阻塞操作,这样就不会出现多个协程同时执行这项工作。
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);
}
我们可以在
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));
}
std::mutex
保护 co_await 的协程这个与示例1有类似的问题,只不过临界区调用了
co_await
。
示例 1 似乎是一种反模式,即在协程中调用阻塞函数,但我想知道会发生什么?假设协程 A 获取互斥体并调用
call_blocking_io
。由于它是阻塞的,我认为调度程序将切换到另一个协程,例如不幸的是,B 与协程 A 调度在同一线程上,并尝试锁定互斥体。这会导致同一线程尝试两次锁定非递归互斥锁,并导致其未定义的行为。
示例 2 尝试将互斥锁锁定在
await_suspend
中。如果无法获取锁,就会阻塞。在这种情况下,调度程序是否会切换到另一个协程?如果要切换,我认为它与示例1有相同的问题。
示例3已经有了答案和解决方案。我想知道,4年过去了,情况是否仍然如此? C++ 20 是否发布了一些功能来解决这个问题?
经典和异步/协程编码风格确实不能很好地互操作。
除了切换到异步本机锁和原语之外,在这些不同的世界边界上还采取了一些折衷措施:
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();
}