保护一部分代码不会在协程中同时执行

时间:2018-05-29 09:24:52

标签: c++ visual-c++

我需要保护一段代码不会在协程中同时执行。防止多线程环境中的并发执行将是使用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实现。

1 个答案:

答案 0 :(得分:1)

C ++或标准库没有提供基础结构来获取所需的功能。但是,Coroutine TS提供了构建块来实现co_await能够的异步互斥锁。

一般的想法是实现一个等待的,试图在评估await_suspend表达式时获取一个合适的互斥量。如果无法获取锁定,则协程将被挂起并添加到等待者队列中,否则会立即继续执行(保持锁定)。

互斥'unlock方法从队列中恢复等待者,除非等待者的队列为空。

网上有预先构建的解决方案。我和Lewis Baker的async_mutex实施有很多原因:

  • 没有内部依赖的外部。只需将编译单元和头文件放入项目中即可完成。
  • 该锁由coroutine拥有,而不是一个线程。该实现允许协程在不同的线程上恢复。
  • 这是一个无锁实现。

此实现的使用与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.
        }
    }
}