使用子守护进程进行Perl守护进程

Kos*_*ndr 5 perl daemon

我必须在我的代码中使用守护进程.我需要一个控制守护进程,它不断检查数据库中的任务并监督子守护进程.控制守护进程必须将任务分配给子守护进程,控制任务,如果其中一个死亡,则创建新子进程等.子守护进程检查数据库以查找它们的任务(通过PID).我应该如何为此目的实现守护进程?

Sob*_*que 12

守护进程只是"长时间运行的后台进程"的代码.所以答案是'它取决于'.Perl有两种​​主要的多处理方式:

穿线

您将子程序作为线程运行,与主程序代码并行运行.(然后可以监视线程状态).

创建线程的开销更高,但它更适合"共享内存"样式的多处理,例如,当您来回传递大量数据时.有几个库使得线程之间的信息传递非常简单.我个人非常喜欢Thread::Queue,Thread::Semaphore而且Storable.

特别是- Storablefreezethaw它可以让你移动在队列中围绕复杂的数据结构(例如对象/哈希),这是非常有用的.

基本线程示例:

#!/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'. E.g.
#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";
}
Run Code Online (Sandbox Code Playgroud)

可收纳

谈到Storable,我认为值得一个单独的例子,因为移动数据很方便.

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();
Run Code Online (Sandbox Code Playgroud)

因为您在队列中传递对象,所以您可以在线程之间有效地克隆它.所以请记住,一旦你对它的内部状态做了某些事情,你可能需要冻结它并以某种方式"返回"它.但它确实意味着您可以异步执行此操作,而无需仲裁锁定或共享内存.您可能还会发现能够"存储"和"检索"和对象很有用 - 这可以像您期望的那样工作.(虽然我敢说你可能需要注意模块版本与定义属性的可用性,如果你正在检索存储的对象)

分叉

你的脚本克隆自己,留下一个"父母"和"孩子" - 然后孩子通常会发散并做出不同的事情.这使用内置的Unix,fork()因此经过优化并且通常非常高效 - 但由于它的低级别,意味着很难进行大量的数据传输.你最终会做一些稍微复杂的事情来进行进程间通信 - IPC.(参见perlipc更多细节).它的效率不仅仅是因为大多数fork()实现都进行了懒惰的数据复制 - 您的进程的内存空间仅在需要时分配,例如在更改时.

因此,如果您想委派许多不需要父母监督的任务,那将是非常好的.例如 - 您可能fork是一个Web服务器,因为孩子正在读取文件并将它们传递给特定的客户端,而父母并不在意.或者,如果您想花费大量CPU时间来计算结果,并且只将结果传回去,那么您可能会这样做.

Windows上也不支持它.

有用的库包括 Parallel::ForkManager

"分叉"代码的基本示例看起来有点像这样:

#!/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();
Run Code Online (Sandbox Code Playgroud)

哪个适合你?

因此,如果没有更多关于你想要实现的目标的细节,很难说.这就是为什么StacKOverflow通常喜欢展示一些工作,你尝试过的方法等等.

我一般会说:

  • 如果需要传递数据,请使用线程.Thread::Queue特别是当与Storable它结合使用时非常好.

  • 如果你不这样做,那么forks(在Unix上)通常更快/更有效.(但是单独快速通常不够 - 首先要写出可理解的东西,然后以速度为目标.通常并不重要).

避免在可能的情况下产生太多线程 - 它们在内存和创建开销方面相当密集.在"工作线程"编程风格中使用固定数字比在重复创建新的短期线程方面要好得多.(另一方面 - 叉子实际上非常擅长这一点,因为它们不会复制整个过程).

我建议在你给出的场景中 - 你正在看线程和队列.您的父进程可以通过threads -> list()和/ join或跟踪子线程create来保持正确的数字.并且可以通过中央队列将数据提供给您的工作线程.或者有多个队列 - 每个'孩子一个'并将其用作任务分配系统.