Pull to refresh

Использование нативных корутин в Java

Reading time2 min
Views13K
Многие современные языки поддерживают работу с корутинами на уровне языка. 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
Tags:
Hubs:
+7
Comments18

Articles

Change theme settings