Решил выложить небольшую заготовку для скрипта, обрабатывающего данные с помощью нескольких дочерних процессов. Мне уже приходилось пару раз ее использовать, может и вам пригодится.
Идея следующая. Есть много дочерних процессов, предназначенных непосредственно для обработки данных, и родительский процесс, который собственно создает дочерние, если нужно — синхронизирует их, и так далее.
Для начала — код родителя, parent.pl:
# parent.pl — (c) 2010 Alexandr A Alexeev, http://remontka.com/
use Socket ;
use IO :: Select ;
use IO :: Handle ;
use strict ;
my $select = IO :: Select -> new ( ) ;
# команды для запуска дочерних процессов
my @commands ; # («ls -la /», «pkg_info | grep p5-«);
print «List of commands: n n » ;
# тут заполняешь массив @commands
# while(1) {
# my $cmd = «./child.pl arg1 arg2 arg3»;
# print «$cmdn»;
# push @commands, $cmd;
# }
print » n starting in 10 seconds… n n » ;
sleep 10 ;
for my $cmd ( @commands ) {
print «starting ‘$cmd’… n » ;
my ( $hchild , $hparent , $childid ) ;
socketpair ( $hchild , $hparent , AF_UNIX , SOCK_STREAM , PF_UNSPEC )
or die «socketpair: $!» ;
$childid = fork ;
die «cannot fork» if ( $childid == — 1 ) ;
# перенаправляем ввод/вывод потомка в сокет
unless ( $childid ) {
# потомок
open STDIN , «<&» , $hparent ;
open STDOUT , «>&» , $hparent ;
open STDERR , «>&» , $hparent ;
close $hparent ;
close $hchild ;
# унаследованные хэгдлы следует закрыть
$select -> remove ( $_ ) and close $_ for ( $select -> handles ) ;
exec $cmd ;
}
close $hparent ;
$select -> add ( $hchild ) ;
}
print «All done, now reading… n » ;
while ( my @ready = $select -> can_read ) {
# кто-то из потомков завершился или что-то написал в stdout
for my $read ( @ready ) {
if ( $read -> eof || $read -> error ) {
# потомок завершил работу
$select -> remove ( $read ) ;
close $read ;
next ;
}
if ( my $str = < $read > ) {
print $str ;
# обрабатываем строку $str
}
}
}
В массив @commands помещаются те команды, которые мы хотим выполнить. Скорее всего, это будет child.pl с различными аргументами (имя входного файла, к примеру).
Затем для каждой команды скрипт создает пару сокетов, форкатеся, перенаправляет ввод/вывод потомка в один из сокетов и делает exec(). Считаем, что все необходимые данные дочерние скрипты будут выводить в STDOUT, откуда родитель сможет считать их через сокет.
Вот заготовка для child.pl:
use strict ;
$| = 1 ; # <— архиважная строчка, отключающая буферизацию вывода
# здесь тупо парсим аргументы и выводим все, что надо в stdout
Как видите, вся заготовка фактически заключается в единственной строке под номером пять. Присвоив переменной с именем «вертикальная черта» значение 1, мы отключаем буферизацию вывода дочернего скрипта — все данные будут передаваться родителю безо всяких задержек.
Кстати, интересная особенность ОС семейства UNIX — не существует приема (патчинг бинарных файлов и прочие извращения не предлагать), позволяющего произвести описанные действия для произвольной программы. Необходимо, чтобы возможность отключения кэширования была предусмотрена в исходном коде программы. Например, некоторые утилиты имеют для этого специальный ключ.
Ну вот пожалуй и все, что я хотел сказать в этой заметке. В качестве дополнительных источников информации рекомендую мою серию уроков по Perl (разумеется!), а также «man IO::Select» и книгу «UNIX — профессиональное программирование» Ричарда Стивенса. Несмотря на свой объем, читается книга на одном дыхании. Не так давно она была переиздана на русском языке.
Дополнение: В принципе можно использовать и threads::shared , но я бы не советовал .
Дополнение: См существенно доработанную версию скрипта в пункте 2 заметки Как написать своего паука .