В С++20 планируется ввести поддержку для работы с корутинами.
Используя JNI мы можем писать корутины на С++ и использовать в Java коде.
Рассмотрим какие нативные корутины можно писать и как их использовать в Java коде.
Генератор позволяет создавать последовательность значений определенного типа, при этом значения генерируются лениво и синхронно.
/* C++ code */ generator<int> generate(int count) { for (int i = 0; i < count; i++) { co_yield i; } }
/* Java code */ Generator<Integer> gen1 = Coroutine.yield(5); Generator<Float> gen2 = Coroutine.yield(1f, 5); Generator<Double> gen3 = Coroutine.yield(v -> v * 2, 1d, 5); for (int item : gen1) { System.out.println("yield value: " + item); }
Асинхронный Генератор позволяет создавать последовательность значений определенного типа, при этом значения генерируются лениво и асинхронно.
/* C++ code */ async_generator<int> generate(int count) { for (int i = 0; i < count; i++) { co_await 1s; co_yield i; } }
/* Java code */ Generator<Integer> gen1 = Coroutine.yieldAsync(5); Generator<Float> gen2 = Coroutine.yieldAsync(1f, 5); Generator<Double> gen3 = Coroutine.yieldAsync(v -> v * 2, 1d, 5); for (int item : gen1) { System.out.println("yield value: " + item); }
Задача (Task) производит асинхронное вычисление, которое выполняется лениво, при этом корутина не выполняется, пока задача не запустится явно.
Корутины можно использовать как легковесные потоки. При этом количество запущенных потоков в системе может быть ограничено, например не более 1000. А корутин можно запустить сколько угодно.
При запуске корутины, проверяется готова ли она. Если нет, то корутина приостанавливается и ОС передается обработчик на ее. В этот момент выполниться полезный кусок кода. Когда корутина готова, тогда выполняется возобновление корутины.
/* C++ code */ struct awaiter { bool await_ready() const { return false; } void await_resume() {} void await_suspend(std::coroutine_handle<> handler) { /* invoke java/jni code */ if (!handler.done()) { handler.resume(); } } }; co_await awaiter{};
Как при запуске потока, корутине можно передать Runnable или Callable.
/* Java code */ Coroutine.await(() -> { int sum = 5 + 10; }); Task<Integer> task = Coroutine.await(() -> { int sum = 5 + 10; return sum; });
Таймер выполняет приостановку выполнения текущей задачи на требуемую длительность.
auto operator co_await(const std::chrono::system_clock::duration& duration) { return timer{duration}; } co_await 10ms;
Можно использовать как замену Thread.sleep().
Coroutine.await(10, TimeUnit.MILLISECONDS);
Также корутины можно применять для написания не блокирующего кода для работы с файловой системой, сетью и т.д.
Как можно видеть корутины облегчают написание асинхроного кода, позволяя выполнять части кода не блокируя поток.
Корутины которые планируют завести в С++20, появлятся в виде чистой языковой фичи.
Генераторы, задачи и другие корутиные планируются добавить в стандарт С++23 или позже.
Можно самому писать свои корутины или использовать уже готовой библиотекой, например cppcoro.
Компиляторы MVSC, Clang уже поддерживают корутины как расширение, а GCC только на стадии разработки.
Полный исходной код можно посмотреть на github: code

