我需要保护一段代码不会在协程中同时执行。防止多线程环境中的并发执行将是使用std::lock_guard类模板的简单问题。但是,我的协程是从单个线程调用的,因此该解决方案不适用。
以下是我试图完成的(伪)代码:
future<http_response> send_req_async(http_request req) {
while (true) {
// Attempt to send an HTTP request
auto const& access_token{ token_store::access_token() };
auto const response{ co_await impl::send_req_async(req, access_token) };
if (response.status_code() == http_code::ok) {
co_return response;
}
// Attempt to refresh access token
if (response.status_code() == http_code::unauthorized) {
// The following scope needs to be guarded against concurrent execution.
// To guard against concurrent execution from multiple threads I would use:
// lock_guard<mutex> guard(refresh_token_mutex);
if (access_token != token_store::access_token()) {
continue;
}
auto const& token{ co_await refresh_token(token_store::refresh_token()) };
token_store::save_access_token(token);
// End of section that needs to be guarded.
}
}
}
该代码旨在允许并行发出多个请求,同时仅允许单个协程调用尝试刷新过期的访问令牌。理想情况下,解决方案应暂停并发协程调用,而令牌刷新操作正在进行中,并在之后自动恢复(即多线程环境中std::lock_guard
的语义相同)。
协程机器或C ++标准库中是否有任何内容可以让我以干净的方式实现它,或者我必须自己动手实现?
注意:我使用的是Visual Studio 2017 15.7.2,因此您可以完全支持C ++ 17及其Coroutine TS实现。
答案 0 :(得分:1)
C ++或标准库没有提供基础结构来获取所需的功能。但是,Coroutine TS提供了构建块来实现co_await
能够的异步互斥锁。
一般的想法是实现一个等待的,试图在评估await_suspend
表达式时获取一个合适的互斥量。如果无法获取锁定,则协程将被挂起并添加到等待者队列中,否则会立即继续执行(保持锁定)。
互斥'unlock
方法从队列中恢复等待者,除非等待者的队列为空。
网上有预先构建的解决方案。我和Lewis Baker的async_mutex
实施有很多原因:
此实现的使用与std::lock_guard
:
#include <cppcoro/async_mutex.hpp>
namespace {
cppcoro::async_mutex refresh_mutex;
}
future<http_response> send_req_async(http_request req) {
while (true) {
// Attempt to send an HTTP request
auto const& access_token{ token_store::access_token() };
auto const response{ co_await impl::send_req_async(req, access_token) };
if (response.status_code() == http_code::ok) {
co_return response;
}
// Attempt to refresh access token
if (response.status_code() == http_code::unauthorized) {
// The following scope needs to be guarded against concurrent execution.
auto const refresh_guard{ co_await refresh_mutex.scoped_lock_async() };
if (access_token != token_store::access_token()) {
continue;
}
auto const& token{ co_await refresh_token(token_store::refresh_token()) };
token_store::save_access_token(token);
// refresh_guard falls out of scope, unlocking the mutex.
// If there are any suspended coroutines, the oldest one gets resumed.
}
}
}