Выбрать главу

В следующей главе мы рассмотрим концепцию параллелизма и то, как она может позволить нашему приложению обрабатывать множество файлов.

6. Параллелизм (Concurrency)

В некоторых сценариях программе может потребоваться обработка нескольких фрагментов работы, например суммирование количества строк в серии файлов. Это прекрасный пример проблемы, которую может помочь решить параллелизм, позволяя программе выполнять фрагменты работы, ожидая выполнения других. В этой главе мы узнаем, как работает параллелизм в Crystal, и рассмотрим следующие темы:

• Использование волокон для одновременного выполнения работы

• Использование каналов для безопасной передачи данных

• Одновременное преобразование нескольких файлов

К концу этой главы вы сможете понять разницу между параллелизмом и параллелизмом, как использовать волокна для выполнения нескольких одновременных задач и как использовать каналы для правильного обмена данными между волокнами. Вместе эти концепции позволяют создавать многозадачные программы, что приводит к повышению производительности кода.

Технические требования

Прежде чем мы углубимся в эту главу, в вашей системе должно быть установлено следующее:

• Рабочая установка Crystal

• Рабочая установка jq

Инструкции по настройке Crystal можно найти в Главе 1 «Введение в Crystal». Обратите внимание, что jq, скорее всего, можно установить с помощью менеджера пакетов в вашей системе. Однако вы также можете установить его вручную, загрузив с https://stedolan.github.io/jq/download.

Все примеры кода, использованные в этой главе, можно найти в папке Chapter 6 на GitHub по адресу https://github.com/PacktPublishing/Crystal-Programming/tree/main/Chapter06.

Использование волокон (fibers ) для одновременного выполнения работы

Волокно представляет собой часть работы, которая должна выполняться либо одновременно с другими волокнами, либо в какой-то момент в будущем, когда появятся свободные циклы. Они похожи на потоки операционной системы, но более легкие и управляются изнутри Crystal. Прежде чем мы углубимся, важно отметить, что параллелизм — это не то же самое, что параллелизм, но они связаны между собой.

В параллельном коде на различные фрагменты работы тратится немного времени, при этом в определенный момент времени выполняется только часть работы. С другой стороны, параллельный код позволяет одновременно выполнять несколько фрагментов работы. На практике это означает, что по умолчанию одновременно выполняется только одно волокно. В Crystal есть поддержка параллелизма, который позволяет одновременно выполнять более одного волокна, но он все еще считается экспериментальным. По этой причине мы собираемся сосредоточиться на параллелизме.

Мы уже использовали волокна под капотом во всем коде, с которым работали до сих пор. Весь код Crystal выполняется внутри собственного основного волокна. Кроме того, мы можем создавать собственные волокна с помощью метода spawn, который принимает блок, представляющий работу, которую необходимо выполнить в этом волокне. В качестве примера возьмем следующую программу:

puts "Hello program!"

spawn do

   puts "Hello from fiber!"

end

puts "Goodbye program!"

Если бы вы запустили это приложение, оно выдало бы следующее:

Hello program!

Goodbye program!

Но подождите! Что случилось с сообщением в fiber, которое мы создали? Ответ можно найти в начале главы, в разделе "Определение fiber". Ключевые слова появятся в какой-то момент в будущем. Создание fiber не приводит к немедленному выполнению fiber. Вместо этого он запланирован для выполнения планировщиком Crytal. Планировщик выполнит следующий поставленный в очередь fiber при первой возможности. В этом примере такой возможности никогда не возникает, поэтому fiber никогда не выполняется.

Это важная деталь для понимания того, как работает параллелизм в Crystal, а также того, почему природа IO, рассмотренная в Главе 5 "Операции ввода/вывода", может быть настолько полезной. К числу факторов, которые могут привести к выполнению другого fiber, относятся следующие:

• Метод sleep

Fiber.yield метод

• Операции, связанные с IO, такие как чтение/запись в файл или сокет

• Ожидание получения значения из канала

• Ожидание отправки значения в канал

• Когда текущее волокно завершит выполнение

Все эти параметры блокируют волокно, в результате чего другие волокна получают возможность выполниться. Например, добавьте sleep 1 после блока появления и перезапустите программу. Обратите внимание: на этот раз Hello from fiber! действительно печатается. Метод sleep сообщает планировщику, что он должен продолжить выполнение основного волокна через одну секунду. Тем временем он может свободно выполнить следующее волокно в очереди, которое в данном случае печатает наше сообщение.