如何将perl子例程排队到线程队列而不是数据?

时间:2013-04-25 13:03:37

标签: multithreading perl queue

背景
在阅读如何多线程我的perl脚本时,我读了(来自http://perldoc.perl.org/threads.html#BUGS-AND-LIMITATIONS

  

在大多数系统中,频繁且不断地创造和破坏   线程可以导致内存占用量的不断增长   Perl翻译。虽然启动线程很简单   然后 - > join()或 - > detach()它们,对于长期存在的应用程序,它是   更好地维护一个线程池,并将它们重用于工作   需要,使用队列通知线程待处理的工作。

我的剧本将是长寿的;它是一个始终运行的PKI LDAP目录监视守护程序。如果企业监控解决方案因任何原因停止运行,它将生成警报。我的脚本将检查我是否可以访问另一个PKI LDAP目录,以及验证两者的撤销列表
问题:我在谷歌上找到的所有内容都显示将变量(例如标量)传递给线程队列而不是子程序本身......我想我只是不理解如何正确地实现线程队列如何实现一个线程(没有队列)。

问题1 :如何“维护一个线程池”以避免perl解释器慢慢消耗掉越来越多的内存?
问题2 :(不相关,但我发布了此代码)主程序结束时是否有安全的睡眠量,以便我不会在一分钟内启动一次以上的线程? 60看起来很明显,但如果循环速度很快,可能会导致它运行不止一次,或者由于处理时间或某些原因可能会错过一分钟?
提前致谢!

#!/usr/bin/perl

use feature ":5.10";
use warnings;
use strict;
use threads;
use Proc::Daemon;
#

### Global Variables
use constant false => 0;
use constant true  => 1;
my $app = $0;
my $continue = true;
$SIG{TERM} = sub { $continue = false };

# Directory Server Agent (DSA) info
my @ListOfDSAs = (
    { name => "Myself (inbound)",
      host => "ldap.myco.ca",
      base => "ou=mydir,o=myco,c=ca",
    },
    { name => "Company 2",
      host => "ldap.comp2.ca",
      base => "ou=their-dir,o=comp2,c=ca",
    }
);    
#

### Subroutines

sub checkConnections
{   # runs every 5 minutes
    my (@DSAs, $logfile) = @_;
    # Code to ldapsearch
    threads->detach();
}

sub validateRevocationLists
{   # runs every hour on minute xx:55
    my (@DSAs, $logfile) = @_;
    # Code to validate CRLs haven't expired, etc
    threads->detach();
}

#

### Main program
Proc::Daemon::Init;

while ($continue)
{
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

    # Question 1: Queues??

    if ($min % 5 == 0 || $min == 0)
        { threads->create(&checkConnections, @ListOfDSAs, "/var/connect.log"); }

    if ($min % 55 == 0)
        { threads->create(&validateRevocationLists, @ListOfDSAs, "/var/RLs.log"); }

    sleep 60; # Question 2: Safer/better way to prevent multiple threads being started for same check in one matching minute?
}

# TERM RECEIVED
exit 0;
__END__

2 个答案:

答案 0 :(得分:3)

use threads;
use Thread::Queue 3.01 qw( );

my $check_conn_q      = Thread::Queue->new();
my $validate_revoke_q = Thread::Queue->new();

my @threads;
push @threads, async {
   while (my $job = $check_conn_q->dequeue()) {
      check_conn(@$job);
   }
};
push @threads, async {
   while (my $job = $validate_revoke_q->dequeue()) {
      validate_revoke(@$job);
   }
};

while ($continue) {
   my ($S,$M,$H,$m,$d,$Y) = localtime; $m+=1; $Y+=1900;

   $check_conn_q->enqueue([ @ListOfDSAs, "/var/connect.log" ])
      if $M % 5 == 0;

   $validate_revoke_q->enqueue([ @ListOfDSAs, "/var/RLs.log" ])
      if $M == 55;

   sleep 30;
}

$check_conn_q->end();
$validate_revoke_q->end();
$_->join for @threads;

我不确定这里是否需要并行化。如果不是,你可以简单地使用

use List::Util qw( min );

sub sleep_until {
   my ($until) = @_;
   my $time = time;
   return if $time >= $until;
   sleep($until - $time);
}

my $next_check_conn = my $next_validate_revoke = time;
while ($continue) {
   sleep_until min $next_check_conn, $next_validate_revoke;
   last if !$continue;

   my $time = time;
   if ($time >= $next_check_conn) {
      check_conn(@ListOfDSAs, "/var/connect.log");
      $next_check_conn = time + 5*60;
   }

   if ($time >= $next_validate_revoke) {
      validate_revoke(@ListOfDSAs, "/var/RLs.log");
      $next_validate_revoke = time + 60*60;
   }
}

答案 1 :(得分:1)

我建议您一次只运行一次检查,因为这里似乎没有令人信服的理由使用线程,并且您不希望为将要一直运行的程序添加不必要的复杂性

如果您想了解如何使用线程池,可以使用examples included with the threads module。还有Thread::Pool module可能有用。

至于确保你不会在同一分钟内重复检查,你是正确的sleeping 60秒是不合适的。无论你选择睡觉什么价值,你都会遇到失败的边缘情况:它会稍微短于一分钟,你偶尔会在同一分钟内进行两次检查,或者会略长于一分钟,你偶尔会错过一张支票。

相反,使用变量来记住上次完成任务的时间。然后,您可以使用更短的睡眠时间,而无需担心每分钟多次检查。

my $last_task_time = -1;
while ($continue)
{
    my $min = (localtime(time))[1];

    if ($last_task_time != $min && 
          ($min % 5 == 0 || $min > ($last_task_time+5)%60))
    { 
        #Check connections here.

        if ($min == 55 || ($last_task_time < 55 && $min > 55))
        { 
           #Validate revocation lists here.
        }

        $last_task_time = $min;
    }
    else
    {
        sleep 55; #Ensures there is at least one check per minute.
    }
}

更新:我修复了代码,以便在上一个任务运行时间过长时恢复。如果它偶尔需要很长时间,这将是好的。但是,如果任务经常花费超过五分钟,则需要一个不同的解决方案(在这种情况下,线程可能会有意义)。