背景:
在阅读如何多线程我的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__
答案 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.
}
}
更新:我修复了代码,以便在上一个任务运行时间过长时恢复。如果它偶尔需要很长时间,这将是好的。但是,如果任务经常花费超过五分钟,则需要一个不同的解决方案(在这种情况下,线程可能会有意义)。