Многие современные языки поддерживают работу с корутинами на уровне языка. Java в данный момент не поддерживает корутины, но есть надежды что в будущем все может измениться.
В С++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
Автор: koowaah