Асинхронное программирование на Crystal

Язык Crystal: понятная асинхронная модель, простота запуска программы и понятные сообщения компилятора. Пример асинхронной программы.

Crystal не поддерживает полную параллельность, как Go. В нем реализована асинхронная модель, под капотом работающая, похоже на asyncio в Python, но в отличие от Python, все сделано так, что программисту намного легче понять и использовать ее. Дело в том, что в Crystal в любой программе автоматически имеется неявный цикл событий. Это значит, что там, где в Python нужно импортировать asyncio, создавать цикл, еще какие-то пассы руками, а потом любую функцию, в которой предполагается делать неблокирующие ожидания await оборачивать в async - в Crystal всего этого делать не нужно: цикл уже есть, уже имеется одно основное волокно (как поток в Python) и в стандартной библиотеке все операции ввода- вывода, сетевые операции, работа с базами данных - все, что по определению будет чего-то ожидать - все уже написано в асинхронной манере. Что остается сделать программисту: ему остается выделять в нужных местах программы новые волокна через spawn, обмениваться между волокнами данными с помощью каналов Channel (выглядящих так же как в Go) и следить, чтобы основное волокно не закончило свою работу до того, как закончится какая-то работа в выделенных волокнах.

Для создания заготовки проекта выполним

crystal init app crystal_prog_1 Открываем в редакторе файл crystal_prog_1.cr и пишем программу. Пусть программа каждые пять секунд выводит (puts) в консоль приглашение написать что-нибудь, а как только пользователь ввел что-то (gets) и нажал Enter - печатает в ответ введенную пользователем строку. При вводе символа q программа завершит (exit) выполнение.

Понятно, что здесь требуется асинхронность: если писать программу линейно - она будет блокироваться на пять секунд. Нам же нужно, чтобы она реагировала на пользовательский ввод сразу.

Находим в документации ( https://crystal-lang.org/reference/guides/concurrency.html ) главу про конкурентное программирование (и конечно смотрим и в другие главы) и пишем код.

Нам нужны два канала, через которые будут проходить сообщения из двух разных отспавненных волокон, в каждом из которых крутится свой цикл. Внешне модель похожа на то, как это делается в Elixir (Erlang) и Go, с разницей в том, что здесь нет распараллеливания по процессорам. Зато есть небольшой бинарник на выходе, чего нет в Elixir и приятный синтаксис, чего не скажешь про Go. Итак, работают два созданных программистом волокна: одно каждые пять секунд посылает в предварительно созданный канал ticks единички, а второе в цикле ждет ввода от пользователя, и посылает введенный текст в заранее созданный канал user_input. В основном волокне нужно в цикле читать из обоих этих каналов, но так, чтобы ожидание чтения из одного канала не блокировало чтение из второго. Это можно сделать с помощью функции receive_first. Она принимает список каналов, реализована в стандартной библиотеке так: последовательно проверяет каждый из каналов на наличие данных для чтения и как только такой канал нашелся, читает из него одно значение и возвращает.

# Внимание! Возможно, вообще неидиоматичный код, написанный новичком.
module CrystalProg1
  VERSION = "0.1.0"

  user_input = Channel(String).new

  spawn do
    while true
      a = gets()
      if a.is_a?(String)
        user_input.send a
      end
    end
  end

  ticks = Channel(Int32).new

  spawn do
    while true
      ticks.send(1)
      sleep 5
    end
  end

  while true
    res = Channel.receive_first([user_input, ticks])

    if res.is_a?(String)
      if res == "q"
        exit
      end
      puts "You type:'#{res}''"
    end

    if res.is_a?(Int32)
      puts "Type something"
    end
  end
end

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

crystal run src/crystal_prog_1.cr

из корня проекта.

Выводы.

Синтаксис - очень интуитивный. Очень простая модель конкурентного выполнения. Несравнимо проще, чем asyncio в Python. С другой стороны - стандартная библиотека, хоть и выглядит очень мощно, но как получить справку по тем же Channel - непонятно. В целом, факт того что задача оказалась по силам новичку - мотивирует на дальнейшие исследования.

2020-01-05


Блог

COPYRIGHT Close Screen. ( close.screen AT gmail ).