Ключевое слово 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 сокет.
Параллелизм не сильно поможет, если часть работы связана с процессором. Однако в нашем случае чтение/запись данных в/из файлов является проблемой, связанной с вводом-выводом, что делает его идеальным кандидатом для демонстрации некоторых функций параллелизма.
Возвращаясь к нашей логике обработки нескольких файлов, давайте сначала создадим непараллельную реализацию, а затем реорганизуем ее, чтобы использовать функции параллелизма, описанные в последних двух разделах.
Прежде чем мы перейдем непосредственно к делу, давайте потратим немного времени на то, чтобы спланировать, что нам нужно сделать, чтобы поддержать это:
• Найдите способ сообщить CLI, что он должен обрабатывать файлы в режиме нескольких файлов.
• Определить новый метод, который будет обрабатывать каждый файл из ARGV.