[Программирование, C++] Корутины C++20 в примерах (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
В преддверии старта курса "C++ Developer. Professional" приглашаем записаться на открытый вебинар на тему "Backend на современном C++".
Также делимся традиционным переводом полезного материала.
Одним из наиболее важных нововведений C++20 являются корутины. Корутина — это функция, которая может быть приостановлена и после этого возобновлена. Функция становится корутиной, если она используете что-либо из следующего:
- оператор co_await, чтобы приостановить выполнение до его возобновления
- ключевое слово co_return, чтобы завершить выполнение и вернуть значение (опционально)
- ключевое слово co_yield, чтобы приостановить выполнение и вернуть значение
Вдобавок тип возвращаемого значения корутины должен удовлетворять определенным условиям. Однако стандарт C++20 определяет только фреймворк для выполнения корутин, но не определяет никаких типов корутин, удовлетворяющих изложенным требованиям. Это означает, что нам нужно либо писать свои собственные, либо полагаться на сторонние библиотеки. В этой статье я покажу, как написать несколько простых примеров с использованием библиотеки cppcoro.Библиотека cppcoro содержит абстракции для корутин C++20, включая задачу (task), генератор (generator) и asyncgenerator. Задача представляет собой асинхронное вычисление, которое выполняется лениво (то есть только тогда, когда корутина ожидается (awaited)), а генератор — это последовательность значений некоторого типа T, которые также создаются лениво (то есть когда вызывается функция begin(), чтобы получить итератор, или на итераторе вызывается оператор ++).Давайте посмотрим на пример. Приведенная ниже функция produitems() является корутиной, потому что она использует ключевое слово co_yield для возврата значения и имеет тип возвращаемого значения cppcoro::generator<std::string>, который удовлетворяет требованиям к корутине-генератору.
#include <cppcoro/generator.hpp>
cppcoro::generator<std::string> produce_items()
{
while (true)
{
auto v = rand();
using namespace std::string_literals;
auto i = "item "s + std::to_string(v);
print_time();
std::cout << "produced " << i << '\n';
co_yield i;
}
}
ПРИМЕЧАНИЕ: функция rand() используется исключительно для простоты примера. Не используйте эту устаревшую функцию в реальном продакшн коде.
Эта функция реализует бесконечный цикл, выполнение которого приостанавливается всякий раз, когда мы достигаем оператора co_yield. Эта функция выдает случайное число при каждом возобновлении выполнения, что происходит при итерировании генератора. Пример показан ниже:
#include <cppcoro/task.hpp>
cppcoro::task<> consume_items(int const n)
{
int i = 1;
for(auto const& s : produce_items())
{
print_time();
std::cout << "consumed " << s << '\n';
if (++i > n) break;
}
co_return;
}
Функция consume_items также является корутиной. Она использует ключевое слово co_return для завершения выполнения, а ее возвращаемый тип — cppcodo::task<>, который также удовлетворяет требованиям для типа корутины. Эта функция запускает цикл n раз, используя for с диапазоном. Этот цикл вызывает функцию begin() класса cppcoro::generator<std::string>, чтобы получить итератор, который впоследствии инкрементируется с помощью оператора ++. produceitems() возобновляется при каждом из этих вызовов и возвращает новое (случайное) значение. Если возникает исключение, оно перебрасывается функции, вызывающей begin() или оператор ++. В produceitems() функция может быть возобновлена бесконечное количество раз, хотя потребляющий код нуждается лишь в конечном числе вызовов.consume_items() может быть вызвана из main(). Однако, поскольку main() не может быть корутиной, она не может использовать оператор co_await для ожидания завершения ее выполнения. Чтобы помочь с этим, библиотека cppcoro предоставляет функцию с именем syncwait(), которая синхронно ожидает завершения указанной awaitable (которая ожидается в текущем потоке внутри вновь созданной корутины). Эта функция блокирует текущий поток до завершения операции и возвращает результат выражения coawait. В случае возникновения исключения оно перебрасывается вызывающей стороне.Следующий фрагмент кода показывает, как мы можем вызывать и ожидать require_items() из main():
#include <cppcoro/sync_wait.hpp>
int main()
{
cppcoro::sync_wait(consume_items(5));
}
Вывод этой программы выглядит следующим образом:
cppcoro::generator<T> генерирует значения ленивым, но синхронным образом. Это означает, что использование оператора co_await из корутины, возвращающей этот тип, невозможно. Однако в библиотеке cppcoro есть асинхронный генератор cppcoro::asyncgenerator<T>, который делает это возможным.Мы можем изменить предыдущий пример следующим образом: возвращать значение, для вычисления которого требуется некоторое время, будет новая корутина next_value(). Мы симулируем такое поведение, ожидая случайное количество секунд. В каждой итерации цикла корутина produce_items() будет ожидать новое значение, а затем возвращать новый элемент на основе этого значения. Тип возврата на этот раз — cppcoro::async_generator<T>.
#include <cppcoro/async_generator.hpp>
cppcoro::task<int> next_value()
{
using namespace std::chrono_literals;
co_await std::chrono::seconds(1 + rand() % 5);
co_return rand();
}
cppcoro::async_generator<std::string> produce_items()
{
while (true)
{
auto v = co_await next_value();
using namespace std::string_literals;
auto i = "item "s + std::to_string(v);
print_time();
std::cout << "produced " << i << '\n';
co_yield i;
}
}
Потребитель требует небольшого изменения, потому что он должен ожидать каждого нового значения. Это реализуется с помощью оператора co_await в цикле for следующим образом:
cppcoro::task<> consume_items(int const n)
{
int i = 1;
for co_await(auto const& s : produce_items())
{
print_time();
std::cout << "consumed " << s << '\n';
if (++i > n) break;
}
}
Оператор co_return больше не присутствует в этой реализации, хотя его можно добавить. Поскольку co_await используется в цикле for, функция является корутиной. Вам не нужно добавлять пустые операторы co_return в конец корутины, возвращающей cppcoro::task<>, точно так же, как вам не нужны пустые операторы return в конце обычной функции, возвращающей void. Предыдущая реализация требовала этого оператора, потому что не было вызова co_await, следовательно, co_return был необходим, чтобы сделать функцию корутиной.Никаких изменений в main() не требуется. Однако, когда мы в этот раз выполняем код, каждое значение генерируется через некоторый случайный интервал времени, как показано на следующем изображении:
Для полноты картины, функция print_time(), упомянутая в этих примерах, выглядит следующим образом:
void print_time()
{
auto now = std::chrono::system_clock::now();
std::time_t time = std::chrono::system_clock::to_time_t(now);
char mbstr[100];
if (std::strftime(mbstr, sizeof(mbstr), "[%H:%M:%S] ", std::localtime(&time)))
{
std::cout << mbstr;
}
}
Еще одна важная деталь, на которую следует обратить внимание, — это то, что вызов co_await с заданной продолжительностью времени по умолчанию невозможен. Однако это стало возможным благодаря перегрузке оператора co_await. В Windows работает следующая реализация:
#include <windows.h>
auto operator co_await(std::chrono::system_clock::duration duration)
{
class awaiter
{
static
void CALLBACK TimerCallback(PTP_CALLBACK_INSTANCE,
void* Context,
PTP_TIMER)
{
stdco::coroutine_handle<>::from_address(Context).resume();
}
PTP_TIMER timer = nullptr;
std::chrono::system_clock::duration duration;
public:
explicit awaiter(std::chrono::system_clock::duration d)
: duration(d)
{}
~awaiter()
{
if (timer) CloseThreadpoolTimer(timer);
}
bool await_ready() const
{
return duration.count() <= 0;
}
bool await_suspend(stdco::coroutine_handle<> resume_cb)
{
int64_t relative_count = -duration.count();
timer = CreateThreadpoolTimer(TimerCallback,
resume_cb.address(),
nullptr);
bool success = timer != nullptr;
SetThreadpoolTimer(timer, (PFILETIME)&relative_count, 0, 0);
return success;
}
void await_resume() {}
};
return awaiter{ duration };
}
Эта реализация взята из статьи Coroutines in Visual Studio 2015 - Update 1.АПДЕЙТ: код был изменен на основе отзывов. Смотрите комментарии.Чтобы узнать больше о корутинах, смотрите:
- C++20 Coroutines
- Exploring MSVC Coroutine
- Coroutine Theory
- C++ Coroutines: Understanding operator co_await
- C++ Coroutines: Understanding the promise type
Узнать подробнее о курсе "C++ Developer. Professional".Записаться на открытый вебинар на тему "Backend на современном C++".
ЗАБРАТЬ СКИДКУ
===========
Источник:
habr.com
===========
===========
Автор оригинала: Marius Bancila
===========Похожие новости:
- [Тестирование IT-систем, Программирование, TDD, Управление разработкой, Читальный зал] Вам не нужны юнит-тесты
- [Программирование, Управление разработкой, Управление персоналом] Парное программирование: стили, организация, тайм-менеджмент (перевод)
- [Программирование, Умный дом, Голосовые интерфейсы] Как разговорить Марусю: FAQ по созданию скиллов для голосового ассистента
- [Разработка веб-сайтов, JavaScript, Программирование] Новые возможности ES2021 / ES12 (перевод)
- [Тестирование IT-систем, Программирование, Kubernetes] Тестирование инфраструктуры как кода Terraform: анализ модульных тестов и сквозной разработки путем тестирования поведен (перевод)
- [Agile] 10 примеров эффективного общения для тестировщиков (перевод)
- [PHP, Программирование, Интервью] Занятное мини-интервью с основными контрибьюторами PHP 8
- [Анализ и проектирование систем, Робототехника, Софт] Вы можете использовать RPA, чтобы помочь с очисткой данных для аналитики (перевод)
- [Программирование, Java, Scala] Scala 3: новый, но необязательный синтаксис (перевод)
- [Программирование, Разработка мобильных приложений, Dart, Flutter] Flutter под капотом: Owners
Теги для поиска: #_programmirovanie (Программирование), #_c++, #_si++ (си++), #_korutiny (корутины), #_bekend (бэкенд), #_blog_kompanii_otus._onlajnobrazovanie (
Блог компании OTUS. Онлайн-образование
), #_programmirovanie (
Программирование
), #_c++
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:20
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
В преддверии старта курса "C++ Developer. Professional" приглашаем записаться на открытый вебинар на тему "Backend на современном C++".
Также делимся традиционным переводом полезного материала.
#include <cppcoro/generator.hpp>
cppcoro::generator<std::string> produce_items() { while (true) { auto v = rand(); using namespace std::string_literals; auto i = "item "s + std::to_string(v); print_time(); std::cout << "produced " << i << '\n'; co_yield i; } } ПРИМЕЧАНИЕ: функция rand() используется исключительно для простоты примера. Не используйте эту устаревшую функцию в реальном продакшн коде.
#include <cppcoro/task.hpp>
cppcoro::task<> consume_items(int const n) { int i = 1; for(auto const& s : produce_items()) { print_time(); std::cout << "consumed " << s << '\n'; if (++i > n) break; } co_return; } #include <cppcoro/sync_wait.hpp>
int main() { cppcoro::sync_wait(consume_items(5)); } cppcoro::generator<T> генерирует значения ленивым, но синхронным образом. Это означает, что использование оператора co_await из корутины, возвращающей этот тип, невозможно. Однако в библиотеке cppcoro есть асинхронный генератор cppcoro::asyncgenerator<T>, который делает это возможным.Мы можем изменить предыдущий пример следующим образом: возвращать значение, для вычисления которого требуется некоторое время, будет новая корутина next_value(). Мы симулируем такое поведение, ожидая случайное количество секунд. В каждой итерации цикла корутина produce_items() будет ожидать новое значение, а затем возвращать новый элемент на основе этого значения. Тип возврата на этот раз — cppcoro::async_generator<T>. #include <cppcoro/async_generator.hpp>
cppcoro::task<int> next_value() { using namespace std::chrono_literals; co_await std::chrono::seconds(1 + rand() % 5); co_return rand(); } cppcoro::async_generator<std::string> produce_items() { while (true) { auto v = co_await next_value(); using namespace std::string_literals; auto i = "item "s + std::to_string(v); print_time(); std::cout << "produced " << i << '\n'; co_yield i; } } cppcoro::task<> consume_items(int const n)
{ int i = 1; for co_await(auto const& s : produce_items()) { print_time(); std::cout << "consumed " << s << '\n'; if (++i > n) break; } } Для полноты картины, функция print_time(), упомянутая в этих примерах, выглядит следующим образом: void print_time()
{ auto now = std::chrono::system_clock::now(); std::time_t time = std::chrono::system_clock::to_time_t(now); char mbstr[100]; if (std::strftime(mbstr, sizeof(mbstr), "[%H:%M:%S] ", std::localtime(&time))) { std::cout << mbstr; } } #include <windows.h>
auto operator co_await(std::chrono::system_clock::duration duration) { class awaiter { static void CALLBACK TimerCallback(PTP_CALLBACK_INSTANCE, void* Context, PTP_TIMER) { stdco::coroutine_handle<>::from_address(Context).resume(); } PTP_TIMER timer = nullptr; std::chrono::system_clock::duration duration; public: explicit awaiter(std::chrono::system_clock::duration d) : duration(d) {} ~awaiter() { if (timer) CloseThreadpoolTimer(timer); } bool await_ready() const { return duration.count() <= 0; } bool await_suspend(stdco::coroutine_handle<> resume_cb) { int64_t relative_count = -duration.count(); timer = CreateThreadpoolTimer(TimerCallback, resume_cb.address(), nullptr); bool success = timer != nullptr; SetThreadpoolTimer(timer, (PFILETIME)&relative_count, 0, 0); return success; } void await_resume() {} }; return awaiter{ duration }; }
Узнать подробнее о курсе "C++ Developer. Professional".Записаться на открытый вебинар на тему "Backend на современном C++".
ЗАБРАТЬ СКИДКУ =========== Источник: habr.com =========== =========== Автор оригинала: Marius Bancila ===========Похожие новости:
Блог компании OTUS. Онлайн-образование ), #_programmirovanie ( Программирование ), #_c++ |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:20
Часовой пояс: UTC + 5