Вам бонус- начислено 1 монета за дневную активность. Сейчас у вас 1 монета

Корутины C++20 в примерах кратко

Лекция



Привет, Вы узнаете о том , что такое корутины, Разберем основные их виды и особенности использования. Еще будет много подробных примеров и описаний. Для того чтобы лучше понимать что такое корутины, корутина , настоятельно рекомендую прочитать все из категории Алгоритмизация и программирование. Структурное программирование. Язык C.

Одним из наиболее важных нововведений C++20 являются корутины . корутина — это функция, которая может быть приостановлена ​​и после этого возобновлена. Функция становится корутиной, если она используете что-либо из следующего:

  • оператор co_await, чтобы приостановить выполнение до его возобновления

  • ключевое слово co_return, чтобы завершить выполнение и вернуть значение (опционально)

  • ключевое слово co_yield, чтобы приостановить выполнение и вернуть значение

Вдобавок тип возвращаемого значения корутины должен удовлетворять определенным условиям. Однако стандарт C++20 определяет только фреймворк для выполнения корутин, но не определяет никаких типов корутин, удовлетворяющих изложенным требованиям. Это означает, что нам нужно либо писать свои собственные, либо полагаться на сторонние библиотеки. В этой статье я покажу, как написать несколько простых примеров с использованием библиотеки cppcoro.

Библиотека cppcoro содержит абстракции для корутин C++20, включая задачу (task), генератор (generator) и async_generator. Задача представляет собой асинхронное вычисление, которое выполняется лениво (то есть только тогда, когда корутина ожидается (awaited)), а генератор — это последовательность значений некоторого типа T, которые также создаются лениво (то есть когда вызывается функция begin(), чтобы получить итератор, или на итераторе вызывается оператор ++).

Давайте посмотрим на пример. Приведенная ниже функция produce_items() является корутиной, потому что она использует ключевое слово 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 также является корутиной. Об этом говорит сайт https://intellect.icu . Она использует ключевое слово co_return для завершения выполнения, а ее возвращаемый тип — cppcodo::task<>, который также удовлетворяет требованиям для типа корутины. Эта функция запускает цикл n раз, используя for с диапазоном. Этот цикл вызывает функцию begin() класса cppcoro::generator<std::string>, чтобы получить итератор, который впоследствии инкрементируется с помощью оператора ++. produce_items() возобновляется при каждом из этих вызовов и возвращает новое (случайное) значение. Если возникает исключение, оно перебрасывается функции, вызывающей begin() или оператор ++. В produce_items() функция может быть возобновлена бесконечное количество раз, хотя потребляющий код нуждается лишь в конечном числе вызовов.

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));
}

Вывод этой программы выглядит следующим образом:

Корутины C++20 в примерах

cppcoro::generator<T> генерирует значения ленивым, но синхронным образом. Это означает, что использование оператора co_await из корутины, возвращающей этот тип, невозможно. Однако в библиотеке cppcoro есть асинхронный генератор cppcoro::async_generator<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() не требуется. Однако, когда мы в этот раз выполняем код, каждое значение генерируется через некоторый случайный интервал времени, как показано на следующем изображении:

Корутины C++20 в примерах

Для полноты картины, функция 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 };
}

Данная статья про корутины подтверждают значимость применения современных методик для изучения данных проблем. Надеюсь, что теперь ты понял что такое корутины, корутина и для чего все это нужно, а если не понял, или есть замечания, то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории Алгоритмизация и программирование. Структурное программирование. Язык C

Из статьи мы узнали кратко, но содержательно про корутины
создано: 2021-03-13
обновлено: 2024-11-13
11



Рейтиг 9 of 10. count vote: 2
Вы довольны ?:


Поделиться:

Найди готовое или заработай

С нашими удобными сервисами без комиссии*

Как это работает? | Узнать цену?

Найти исполнителя
$0 / весь год.
  • У вас есть задание, но нет времени его делать
  • Вы хотите найти профессионала для выплнения задания
  • Возможно примерение функции гаранта на сделку
  • Приорететная поддержка
  • идеально подходит для студентов, у которых нет времени для решения заданий
Готовое решение
$0 / весь год.
  • Вы можите продать(исполнителем) или купить(заказчиком) готовое решение
  • Вам предоставят готовое решение
  • Будет предоставлено в минимальные сроки т.к. задание уже готовое
  • Вы получите базовую гарантию 8 дней
  • Вы можете заработать на материалах
  • подходит как для студентов так и для преподавателей
Я исполнитель
$0 / весь год.
  • Вы профессионал своего дела
  • У вас есть опыт и желание зарабатывать
  • Вы хотите помочь в решении задач или написании работ
  • Возможно примерение функции гаранта на сделку
  • подходит для опытных студентов так и для преподавателей

Комментарии


Оставить комментарий
Если у вас есть какое-либо предложение, идея, благодарность или комментарий, не стесняйтесь писать. Мы очень ценим отзывы и рады услышать ваше мнение.
To reply

Алгоритмизация и программирование. Структурное программирование. Язык C

Термины: Алгоритмизация и программирование. Структурное программирование. Язык C