Guard a section of code from being executed concurrently in a coroutine

喜夏-厌秋 提交于 2021-02-08 07:53:54

问题


I need to protect a section of code from being executed concurrently in a coroutine. Guarding against concurrent execution in a multithreaded environment would be a simple matter of using the std::lock_guard class template. My coroutine, however, is called from a single thread, so that solution is not applicable.

The following is (pseudo) code of what I'm trying to accomplish:

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.
        }
    }
}

The code is meant to allow for several requests being issued in parallel, while allowing only a single coroutine invocation from trying to refresh an expired access token. Ideally, a solution should suspend a concurrent coroutine invocation, while a token refresh operation is in flight, and automatically resume it afterwards (i.e. the same semantics of std::lock_guard in a multithreading environment).

Is there anything built into the coroutine machinery or the C++ Standard Library that allows me to implement this in a clean fashion, or will I have to roll my own?


Note: I'm using Visual Studio 2017 15.7.2, so you can assume full support for C++17, plus its Coroutine TS implementation.


回答1:


There is no infrastructure provided by C++ or the Standard Library to get the required functionality. However, the Coroutine TS provides the building blocks to implement a co_await-able async mutex.

The general idea is to implement an awaitable, that tries to acquire a suitable mutex when evaluating the await_suspend expression. If the lock cannot be acquired, the coroutine is suspended and added to a queue of awaiters, otherwise execution continues immediately (with the lock being held).

The mutex' unlock method resumes an awaiter from the queue, unless the queue of awaiters is empty.

There are pre-built solutions on the web. I went with Lewis Baker's async_mutex implementation for a number of reasons:

  • No external of internal dependencies. Just drop the compilation unit and header file into your project and you are done.
  • The lock is owned by the coroutine, not a thread. The implementation allows for a coroutine to resume on a different thread.
  • It's a lock-free implementation.

Use of this implementation is very similar to that of a 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.
        }
    }
}


来源:https://stackoverflow.com/questions/50580792/guard-a-section-of-code-from-being-executed-concurrently-in-a-coroutine

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!