when v = channel2.receive
puts "Received #{v} from channel2"
when v = channel3.receive
puts "Received #{v} from channel3"
when timeout 3.seconds
puts "Nothing left to process, breaking out"
break
end
end
Эта версия ключевого слова select
аналогична первой, но мы добавили к ней два новых предложения. Один считывает значение из третьего канала, а другой выйдет из цикла, если ни по одному каналу в течение трех секунд не будут получены данные. Вывод этой программы следующий:
Starting fiber 1
Starting fiber 2
Starting fiber 3
Received 3 from channel3
Received 2 from channel2
Received 1 from channel1
Nothing left to process, breaking out
Волокна начинают работать по порядку, но заканчивают в другом порядке из-за разной продолжительности сна. Через три секунды выполняется последнее предложение if
, поскольку ничего не получено, а затем программа завершает работу.
Ключевое слово select
не ограничивается только получением значений. Его также можно использовать при их отправке. Возьмем эту программу в качестве примера:
spawn_receiver = true
channel = Channel(Int32).new
if spawn_receiver
spawn do
puts "Received: #{channel.receive}"
end
end
spawn do
select
when channel.send 10
puts "sent value"
else
puts "skipped sending value"
end
end
Fiber.yield
Запуск этого как есть дает следующий результат:
sent value
Received: 10
Установка флага spawn_receiver
в значение false
и его повторный запуск приводит к пропущенному значению отправки. Причина разницы в выводе связана с поведением send
в сочетании с предложением else
ключевого слова select
. select
проверит каждое предложение if
на наличие того, которое не будет блокироваться при выполнении. Однако в этом случае отправляйте блоки, поскольку нет волокна, ожидающего значения, поэтому предложение else
будет выполнено, поскольку ни одно другое предложение не может быть выполнено без блокировки. Поскольку принимающее волокно не было создано, выполняется последний путь, что приводит к пропуску сообщения. В другом сценарии ожидающий получатель не позволяет блокировать отправку.
Хотя использование каналов и волокон для сигнализации о завершении единицы работы является одним из вариантов их использования, это не единственный вариант использования. Эти две концепции, а также select
, можно объединить для создания довольно мощных шаблонов, таких как разрешение одновременного выполнения только определенного количества волокон, координация состояния между несколькими волокнами и каналами или обработка нескольких независимых фрагментов данных. работать одновременно. Последний имеет дополнительное преимущество: скорее всего, он уже настроен для обработки многопоточных рабочих процессов, поскольку каждое волокно может обрабатываться в отдельном потоке.
На этом этапе мы рассмотрели практически все основные концепции параллелизма в Crystal. Следующим шагом будет применение этих концепций, а также того, что было изучено в предыдущих главах, к нашему приложению CLI для поддержки одновременной обработки нескольких файлов.
Преобразование нескольких файлов одновременно
На данный момент приложение поддерживает файловый ввод, но только из одного файла. Допустимым вариантом использования может быть предоставление нескольких файлов и создание нового файла с преобразованными данными для каждого из них. Учитывая, что логика преобразования привязана к IO, одновременное выполнение этого имеет смысл и должно привести к повышению производительности.
Причина, по которой логика, связанная с IO, и параллелизм так хорошо сочетаются друг с другом, заключается в планировщике Crystal. Когда волокно достигает точки своего выполнения, когда оно зависит от некоторой части данных из IO, планировщик может легко отложить это волокно в сторону до тех пор, пока не поступят эти данные.
Более конкретным примером этого в действии было бы рассмотрение того, как функционирует стандартная библиотека HTTP::Server
. Каждый запрос обрабатывается в отдельном волокне. Из-за этого, если во время обработки запроса необходимо выполнить еще один HTTP-запрос, например, для получения данных из внешнего API, Crystal сможет продолжать обрабатывать другие запросы, ожидая возвращения данных через IO сокет.