x = [1, 2, 3, 4, 5, 6, 7, 8]
y = ' первый
второй
третий
четвертый
пятый
'
z = %w[a b с d e f]
compose(x, у, z) {|a,b,c| p [a, b, c] }
# Выводится:
# [1, ' первый
', 'a']
# [2, ' второй
', 'b']
# [3, ' третий
', 'c']
# [4, ' четвертый
', 'd']
# [5, ' пятый
', 'e']
# [6, nil, 'f']
# [7, nil, nil]
# [8, nil, nil]
Обратите внимание: мы не предполагаем, что все объекты имеют одно и то же число элементов. Если один итератор доходит до конца раньше остальных, то он будет генерировать значения nil
до тех пор, пока не закончит работу «самый длинный» итератор.
Конечно, можно написать и более общий метод, который на каждой итерации будет обрабатывать более одного элемента. (В конце концов, не все итераторы возвращают по одному значению за раз.) Можно было бы в первом параметре передавать число значений для каждого итератора.
Можно также пользоваться произвольными итераторами (а не только стандартным each
). Их имена можно было бы передавать в виде строк, а вызывать с помощью метода send
. Много чего еще можно придумать.
Впрочем, мы полагаем, что приведенного кода достаточно для большинства целей. Вариации на эту тему оставляем читателю в качестве упражнения.
13.2.10. Параллельное рекурсивное удаление
Забавы ради напишем код, который будет удалять дерево каталогов. Процедура рекурсивного удаления использует потоки. Как только обнаруживается очередной подкаталог, мы запускаем новый поток, который будет обходить его и удалять содержимое.
Созданные в ходе работы программы потоки хранятся в массиве threads
. Поскольку это локальная переменная, у каждого потока будет собственная копия массива. Раз к ней может обращаться всего один поток, синхронизировать доступ не надо.
Отметим также, что в блок потока передается полное имя файла fullname
, чтобы не нужно было беспокоиться по поводу того, что поток обращается к переменной, которую кто-то еще изменяет. Поток делает для себя локальную копию fn
этой переменной.
Прежде чем удалять очередной каталог, мы должны дождаться завершения всех созданных в процессе его обхода потоков.
def delete_all(dir)
threads = []
Dir.foreach(dir) do |e|
next if ['.','..'].include? e # Пропустить . и ..
fullname = dir + '/' + e
if FileTest.directory?(fullname)
threads << Thread.new(fullname) {|fn| delete_all(fn) }
else
File.delete(fullname)
end
end
threads.each { |t| t.join }
Dir.delete(dir)
end
delete_all('/tmp/stuff')
Будет ли работать такая программа быстрее, чем ее вариант без потоков? В наших тестах получалось по-разному. Возможно, это зависит от операционной системы и структуры конкретного каталога — глубины, количества файлов и т.д.
13.3. Заключение
Как было сказано, в Ruby не используются платформенные потоки. Программа не станет работать быстрее при наличии нескольких процессоров, но некоторого распараллеливания работы достичь все же можно. Потоки полезны во многих случаях» но писать и отлаживать многопоточную программу довольно трудно, особенно если для получения правильного результата приходится применять изощренные способы синхронизации.
Для синхронизации Ruby предоставляет такие классы, как Mutex
, Monitor
и ConditionVariable
. Имеются также безопасные относительно потоков классы очередей Queue
и SizedQueue
.
В главе 14 мы перейдем от обсуждения техники программирования к решению конкретных задач, а именно сценариев системного администрирования.
Глава 14. Сценарии и системное администрирование
И сказал главный программист: «Даже если программа состоит всего из трех строчек, когда-то ее придется сопровождать».
Программистам часто приходится писать небольшие сценарии для запуска внешних программ и работы с операционной системой на достаточно высоком уровне. Особенно это относится к ОС UNIX, где для повседневной работы составляются многочисленные сценарии на языке интерпретатора команд (shell).
Ruby не всегда удобно использовать в качестве такого «склеивающего» языка, поскольку он задуман как более универсальный инструмент. Но в принципе все, что можно сделать на языке bash (и ему подобных), можно реализовать и на Ruby.
Нередко для этой цели можно воспользоваться каким-то более традиционным языком. Преимущества Ruby в его универсальности, богатстве функций и объектной ориентированности. Предполагая, что найдутся люди, желающие использовать Ruby для взаимодействия с операционной системой на таком