2013-04-25 36 views
4

背景
在阅读如何多线程我的Perl脚本,我读(http://perldoc.perl.org/threads.html#BUGS-AND-LIMITATIONS如何将perl子例程排队到线程队列而不是数据?

在大多数系统中,频繁的和持续的创造和 线程破坏可导致不断增加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__ 
+0

愚蠢的问题:为什么使用线程?为什么不一次检查一个DSA? – 2013-04-25 13:14:57

+0

有效的问题! a)服务器有很多内核,所以我想“为什么不使用它们?”和b)我是一个极客,并且想要为未来的脚本学习正确的线程技术(我讨厌以简单的方式出局)。如果发现问题,子例程将SNMP陷阱发送到集中式监视服务器,所以我的主脚本并不真正在意返回值,所以这感觉就像是要走的路。 – static 2013-04-25 13:42:41

+0

我可以将这些原因联系起来,但我认为在这种情况下,除非有令人信服的理由使用它们,否则不应该增加线程的复杂性。 – 2013-04-25 14:14:22

回答

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; 
    } 
} 
+0

非常感谢!我开始理解很多例子......你将参数/参数发送到队列中!快速的问题与你的第一块代码有关:你在哪里推@threads,async {}我想check_conn和validate_revoke正在调用我的潜艇? (在我的代码中称为checkConnections&validateRevocationLists?) – static 2013-04-25 15:08:26

+0

'async'返回线程(如'thread-> create')。这允许我们稍后“加入”它们。使用可连接的线程而不是使用分离的线程可以让线程在执行某些操作时不会退出。 (' - > end'会确保线程不会启动新的工作,但是。) – ikegami 2013-04-25 15:15:08

+0

'check_conn'是'checkConnections'。我重命名你的潜艇(更传统的命名和保存输入)。我不好。 – ikegami 2013-04-25 15:15:46

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. 
    } 
} 

更新:我固定的代码,这样,如果最后任务运行时间过长就会恢复。如果偶尔需要很长时间,这样可以。但是,如果这些任务经常花费超过五分钟的时间,那么您需要一个不同的解决方案(在这种情况下,线程可能有意义)。