ПрограммированиеСтатьиОбщее

Сопрограммы на С++, начальный уровень

Автор:

Coroutine (сопрограмма, корутина) — функция, выполнение которой можно явно прерываться методами языка программирования. В отличие от прерывания выполнения потока (thread), который реализован в ОС и происходит неявно и в любой момент. Здесь я разбираю в чем преимущество корутин и какие корутины добавлены в C++20.

Предварительно с терминологией можно ознакомиться в Википедии: сопрограмма

Рассмотрим как работает простой код на уровне железа и ОС:

auto buf = new Buffer{ 10_Mb };
ReadFile( "file.name", buf );

В двух строках происходит множество неявных прерываний:

auto buf = new Buffer{ 10_Mb };

Выделяется память для записи файла в RAM. Внутри вызывается аллокатор памяти, который обычно защищен мютексом (wiki). Если в этот же момент в другом потоке происходит еще одно выделение памяти, то мютекс позволит выполниться одному потоку, а другой остановится и будет разбужен ОС спустя какое-то время.

ReadFile( "file.name", buf );

Происходит чтение из файла. Доступ к диску медленный, поэтому ОС останавливает поток и возобновляет его, когда чтение из файла завершится.

Получается в каждой строке кода происходит неявная остановка выполнения потока, что приводит к смене контекста (context switch), который занимает более 1мс. Когда поток остановлен, его место занимает другой поток, сколько он выполняется - неизвестно, а значит нет гарантий, когда именно возобновится выполнение нашего потока.

Подход с использованием потоков оказался слишком медленным, так появились различные асинхронные алгоритмы, например с использованием thread pool и promise:

AllocBuffer( 10_Mb )
  .then( [](Buffer buf)
  {
    ReadFile( "file.name", buf )
    .then( [](Buffer buf, size_t readn)
    {
      // делаем что-то с загруженными данными
    });
  });
+ пример_реализации

Каждая функция здесь — маленькая задача, которая выполняется во внутреннем планировщике (thread pool, job system и тд). Планировщик внутри программы заменяет планировщик ОС, тем самым избавляясь от мютексов и синхронных чтений файлов. Но читаемость кода сильно ухудшается.

Корутины это другой синтаксис для использования внутреннем планировщика задач (thread pool). Они позволяют писать более линейный код, который лучше читается.

auto buf = co_await AllocBuffer( 10_Mb );
size_t readn = co_await ReadFile( "file.name", buf );
... // делаем что-то с загруженными данными
+ пример_реализации

Как и первый код, корутина прерывается два раза - явно при вызове оператора co_await, но остановки потоков не происходит и в промежутках выполняются другие задачи.

Далее разбирается, как реализованы корутины в C++20.

А также приведены простые примеры написания своих корутин, все в однопоточном режиме.
1. Как работает Awaiter
2. Как прерывать и возобновлять выполнение корутины
3. Реализации системы тасков на корутинах
4. Как возвращать значение из корутины
5. Как организовать зависимости между корутинами
6. Как реализовать Awaiter для разных типов

Дополнительные материалы
Документация
git репозиторий с примерами
cppcoro - библиотека с корутинами для файлов, сети и тд

Материалы на английском
Статьи на ModernesCPP
Видео: Understanding C++ Coroutines by Example, Part 1, Part 2

Страницы: 1 2 37 8 Следующая »

#C++

7 ноября 2022 (Обновление: 11 мар 2024)

Комментарии [58]