Защитите часть кода от одновременного выполнения в сопрограмме - PullRequest
0 голосов
/ 29 мая 2018

Мне нужно защитить часть кода от одновременного выполнения в сопрограмме.Защита от одновременного выполнения в многопоточной среде будет простым делом с использованием шаблона класса 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 голосов
/ 01 июня 2018

Нет инфраструктуры, предоставляемой C ++ или стандартной библиотекой для получения требуемой функциональности.Однако Coroutine TS предоставляет строительные блоки для реализации co_await -безопасного асинхронного мьютекса.

Общая идея заключается в реализации ожидаемого, который пытается получить подходящий мьютекс при оценкевыражение await_suspend.Если блокировка не может быть получена, сопрограмма приостанавливается и добавляется в очередь ожидающих, в противном случае выполнение продолжается немедленно (с удерживаемой блокировкой).

Метод mutex 'unlock возвращает ожидающего из очереди, если очередь ожидающих не пуста.

В сети есть готовые решения.Я пошел с реализацией 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.
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...