Параллелизм не сильно поможет, если часть работы связана с процессором. Однако в нашем случае чтение/запись данных в/из файлов является проблемой, связанной с вводом-выводом, что делает его идеальным кандидатом для демонстрации некоторых функций параллелизма.
Возвращаясь к нашей логике обработки нескольких файлов, давайте сначала создадим непараллельную реализацию, а затем реорганизуем ее, чтобы использовать функции параллелизма, описанные в последних двух разделах.
Прежде чем мы перейдем непосредственно к делу, давайте потратим немного времени на то, чтобы спланировать, что нам нужно сделать, чтобы поддержать это:
• Найдите способ сообщить CLI, что он должен обрабатывать файлы в режиме нескольких файлов.
• Определить новый метод, который будет обрабатывать каждый файл из ARGV.
Первое требование можно удовлетворить, поддерживая опцию CLI --multi
, которая переведет его в правильный режим. Второе требование также простое, поскольку мы можем добавить еще один метод к типу Processor
, чтобы также предоставить его для использования библиотекой. Во-первых, давайте начнем с метода Processor
. Откройте
def process_multiple(filter : String, input_files :
Array(String), error : IO) : Nil
input_files.each do |file|
File.open(file, "r") do |input_file|
File.open("#{input_file.path}.transformed", "w") do
|output_file|
self.process [filter], input_file, output_file, error
end
end
end
end
Этот метод сводится к следующим шагам:
1. Определите новый метод, предназначенный для обработки нескольких входных файлов, который принимает фильтр и массив файлов для обработки.
2. Переберите каждый входной файл, используя метод File.open
, чтобы открыть файл для чтения.
3. Снова используйте File.open
, чтобы открыть выходной файл для записи, используя путь к входному файлу с добавлением .transformed
в качестве имени выходного файла,
4. Вызовите метод одиночного ввода, передав наш фильтр в качестве единственного аргумента и используя открытые файлы в качестве входных и выходных операций IO.
Прежде чем мы сможем это протестировать, нам нужно сделать так, чтобы передача опции --multi
заставляла CLI вызывать этот метод. Давайте сделаем это сейчас. Откройте
require "./transform"
require "option_parser"
processor = Transform::Processor.new
multi_file_mode = false
OptionParser.parse do |parser|
parser.banner = "Usage: transform
[arguments] [filename …]"
parser.on("-m", "--multi", "Enables multiple file input mode") { multi_file_mode = true }
parser.on("-h", "--help", "Show this help") do
puts parser
exit
end
end
begin
if multi_file_mode
processor.process_multiple ARGV.shift, ARGV, STDERR
else
processor.process ARGV, STDIN, STDOUT, STDERR
end
rescue ex : RuntimeError
exit 1
end
И снова на помощь приходит стандартная библиотека Crystal в виде типа OptionParser
. Этот тип позволяет вам настроить логику, которая должна выполняться, когда эти параметры передаются через ARGV. В нашем случае мы можем использовать это для определения более удобного интерфейса, который также будет поддерживать параметры -h
или --help
. Кроме того, он позволяет вам реагировать на флаг --multi
без необходимости вручную анализировать ARGV. Код довольно прост. Если флаг передан, мы устанавливаем для переменной multi_file_mode
значение true
, которое используется для определения того, какой метод процессора вызывать.
Чтобы проверить это, я создал несколько простых файлов YAML в корневом каталоге проекта. Не имеет большого значения, что они собой представляют, важно лишь то, что они действительны в формате YAML. Затем я собрал наш двоичный файл и запустил его с помощью ./bin/transform --multi. file1.yml file2.yml file3.yml
, утверждая, что три выходных файла были созданы должным образом. У меня это заняло ~0,1 секунды. Давайте посмотрим, сможем ли мы улучшить это, реализовав параллельную версию метода process_multiple
.