Perl daemonize com daemons filhos

Eu tenho que empregar daemons no meu código. Eu preciso de um daemon de controle que verifica constantemente o database para as tarefas e supervisiona daemons filho. O daemon de controle deve atribuir tarefas aos daemons filhos, controlar tarefas, criar novos filhos se um deles morrer, etc. Os daemons filhos verificam o database em busca de tarefas para eles (por PID). Como devo implementar daemons para esse propósito?

Daemon é apenas uma palavra de código para “processo em segundo plano que é executado por um longo tempo”. Então a resposta é ‘depende’. Perl tem duas maneiras principais de fazer multiprocessing:

Threading

Você executa uma sub-rotina como um thread, em paralelo com o código do programa principal. (Que pode então monitorar apenas os estados do segmento).

A sobrecarga de criar um encadeamento é maior, mas é mais adequada para o multiprocessing de estilo de ‘memory compartilhada’, por exemplo, quando você está passando quantidades significativas de dados para frente e para trás. Há várias bibliotecas que tornam a transmissão de informações entre encadeamentos positivamente direta. Pessoalmente eu gosto bastante de Thread::Queue , Thread::Semaphore e Storable .

Em particular – Storable tem freeze e thaw que lhe permite mover estruturas de dados complexas (por exemplo, objects / hashes) em torno de filas, o que é muito útil.

Exemplo de encadeamento básico:

 #!/usr/bin/perl use strict; use warnings; use threads; use Thread::Queue; my $nthreads = 5; my $process_q = Thread::Queue->new(); my $failed_q = Thread::Queue->new(); #this is a subroutine, but that runs 'as a thread'. #when it starts, it inherits the program state 'as is'. Eg #the variable declarations above all apply - but changes to #values within the program are 'thread local' unless the #variable is defined as 'shared'. #Behind the scenes - Thread::Queue are 'shared' arrays. sub worker { #NB - this will sit a loop indefinitely, until you close the queue. #using $process_q -> end #we do this once we've queued all the things we want to process #and the sub completes and exits neatly. #however if you _don't_ end it, this will sit waiting forever. while ( my $server = $process_q->dequeue() ) { chomp($server); print threads->self()->tid() . ": pinging $server\n"; my $result = `/bin/ping -c 1 $server`; if ($?) { $failed_q->enqueue($server) } print $result; } } #insert tasks into thread queue. open( my $input_fh, "< ", "server_list" ) or die $!; $process_q->enqueue(< $input_fh>); close($input_fh); #we 'end' process_q - when we do, no more items may be inserted, #and 'dequeue' returns 'undefined' when the queue is emptied. #this means our worker threads (in their 'while' loop) will then exit. $process_q->end(); #start some threads for ( 1 .. $nthreads ) { threads->create( \&worker ); } #Wait for threads to all finish processing. foreach my $thr ( threads->list() ) { $thr->join(); } #collate results. ('synchronise' operation) while ( my $server = $failed_q->dequeue_nb() ) { print "$server failed to ping\n"; } 

Armazenável

Quando se trata de Storable, vale a pena um exemplo separado, porque é útil mover dados.

 use Storable qw ( freeze thaw ); use MyObject; #home made object. use Thread::Queue; my $work_q = Thread::Queue->new(); sub worker_thread { while ( my $packed_item = $work_q->dequeue ) { my $object = thaw($packed_item); $object->run_some_methods(); $object->set_status("processed"); #maybe return $object via 'freeze' and a queue? } } my $thr = threads->create( \&worker_thread ); my $newobject = MyObject->new("some_parameters"); $work_q->enqueue( freeze($newobject) ); $work_q->end(); $thr->join(); 

Como você está passando o object pela fila, você está efetivamente clonando-o entre threads. Então, tenha em mente que você pode precisar congelá-lo e “devolvê-lo” de alguma forma, uma vez que você tenha feito alguma coisa em seu estado interno. Mas isso significa que você pode fazer isso de forma assíncrona sem precisar arbitrar o bloqueio ou a memory compartilhada. Você também pode achar útil poder ‘armazenar’ e ‘recuperar’ e object – isso funciona como você poderia esperar. (Embora eu possa dizer que você pode precisar ter cuidado com a disponibilidade de versões de módulo vs. atributos definidos se você estiver recuperando um object armazenado)

Bifurcação

Seu roteiro clona a si mesmo, deixando um ‘pai’ e ‘criança’ – a criança geralmente diverge e faz algo diferente. Isso usa o Unix construído em fork() que, como resultado, é bem otimizado e geralmente muito eficiente – mas, como é baixo, significa que é difícil fazer muita transferência de dados. Você vai acabar com algumas coisas um pouco complicadas para fazer comunicação entre processos – IPC. (Veja perlipc para mais detalhes). É eficiente não menos porque a maioria das implementações fork() fazem uma cópia de dados lenta – o espaço de memory para o seu processo é alocado apenas conforme necessário, por exemplo, quando é alterado.

Portanto, é muito bom se você deseja delegar muitas tarefas que não exigem muita supervisão dos pais. Por exemplo, você pode ter um servidor web, porque o filho está lendo arquivos e entregando-os a um cliente em particular, e o pai não se importa muito. Ou talvez você faça isso se quiser gastar muito tempo computando um resultado e apenas passar esse resultado de volta.

Também não é suportado no Windows.

Bibliotecas úteis incluem Parallel::ForkManager

Um exemplo básico de código ‘forking’ parece um pouco com isto:

 #!/usr/bin/perl use strict; use warnings; use Parallel::ForkManager; my $concurrent_fork_limit = 4; my $fork_manager = Parallel::ForkManager->new($concurrent_fork_limit); foreach my $thing ( "fork", "spoon", "knife", "plate" ) { my $pid = $fork_manager->start; if ($pid) { print "$$: Fork made a child with pid $pid\n"; } else { print "$$: child process started, with a key of $thing ($pid)\n"; } $fork_manager->finish; } $fork_manager->wait_all_children(); 

Qual é o certo para você?

Então é difícil dizer sem mais detalhes sobre o que você está tentando realizar. É por isso que o StacKOverflow geralmente gosta de mostrar alguns methods de trabalho, abordagens que você tentou, etc.

Eu geralmente diria:

  • Se você precisar passar dados, use threads. Thread::Queue especialmente quando combinado com o Storable é muito bom para ele.

  • Se você não o fizer, os garfos (no Unix) são geralmente mais rápidos / mais eficientes. (Mas jejuar sozinho geralmente não é o suficiente – escreva coisas compreensíveis primeiro, e aponte para velocidade em segundo lugar. Não importa muito normalmente).

Evite sempre que possível gerar muitos threads – eles são bastante intensivos em memory e sobrecarga de criação. Você está muito melhor usando um número fixo em um estilo de programação ‘thread de trabalho’ do que repetidamente criando novos encadeamentos de curta duração. (Por outro lado – garfos são realmente muito bons nisso, porque eles não copiam todo o seu processo).

Gostaria de sugerir no cenário que você dá – você está olhando para tópicos e filas. Seu processo pai pode rastrear threads filho através de threads -> list() e join ou create para manter o número correto. E pode alimentar dados através de uma fila central para seus threads de trabalho. Ou tenha várias filas – uma por ‘filho’ e use isso como um sistema de atribuição de tarefas.