Multithreading perl多线程:使用“捕获子线程子线程的stdio”;混合;结果
我用perl编写了一个大规模多线程应用程序,它基本上是扫描文件或目录结构的更改(使用inotify或轮询)。当它检测到更改时,会启动子线程,根据配置执行以更改的文件作为参数的程序 到目前为止,这工作得相当不错,除了我的应用程序还尝试捕获外部执行程序的stdout和stderr,并以结构化方式将它们写入日志文件 然而,我在这里遇到了偶尔但严重的输出混乱,每次线程a上的程序的标准输出(当然,通常是在繁重的工作负载下,正常的测试总是运行良好)都会同时进入线程B上运行的另一个程序的标准输出管道FH 运行外部执行程序并从中捕获输出的线程内代码如下所示:Multithreading perl多线程:使用“捕获子线程子线程的stdio”;混合;结果,multithreading,perl,pipe,stdout,filehandle,Multithreading,Perl,Pipe,Stdout,Filehandle,我用perl编写了一个大规模多线程应用程序,它基本上是扫描文件或目录结构的更改(使用inotify或轮询)。当它检测到更改时,会启动子线程,根据配置执行以更改的文件作为参数的程序 到目前为止,这工作得相当不错,除了我的应用程序还尝试捕获外部执行程序的stdout和stderr,并以结构化方式将它们写入日志文件 然而,我在这里遇到了偶尔但严重的输出混乱,每次线程a上的程序的标准输出(当然,通常是在繁重的工作负载下,正常的测试总是运行良好)都会同时进入线程B上运行的另一个程序的标准输出管道FH 运行
my $out;
$pid = open($out, "( stdbuf -oL ".$cmd." | stdbuf -oL sed -e 's/^/:::LOG:::/' ) 2>&1 |") or xlog('async execution failed for: '.$cmd, LOG_LEVEL_NORMAL, LOG_ERROR);
# catch all worker output here
while(<$out>)
{
if( $_ =~ /^:::LOG:::/ )
{
push(@log, $wname.':::'.$acnt.':::'.time().$_);
} else {
push(@log, $wname.':::'.$acnt.':::'.time().':::ERR:::'.$_);
}
if (time() - $last > 1)
{
mlog($acnt, @log);
$last = time();
@log = ();
}
}
close($out);
waitpid($pid, 0);
push(@log, $wname.':::'.$acnt.':::'.time().':::LOG:::--- thread finished ---');
我的$out;
$pid=open($out,“($cmd.”stdbuf-oL sed-e's/^/:::LOG::/”)2>&1“)或xlog(“:”异步执行失败。$cmd,日志级别正常,日志错误);
#在此处捕获所有工人输出
while()
{
如果($\=~/^:::日志::/)
{
推送(@log,$wname.:'.$acnt.::'.time().$);
}否则{
推送(@log,$wname.:'.$acnt.:'.time().::错误::'.$);
}
如果(时间()-$last>1)
{
mlog($acnt,@log);
$last=time();
@对数=();
}
}
收尾(美元);
waitpid($pid,0);
推送(@log,$wname.:'.$acnt.::'.time().:::日志::---线程已完成--');
这里使用stdbuf来抑制可能的缓冲延迟,并且使用sed管道来避免在读卡器中处理多个fd的需要,同时仍然能够将正常输出与错误分开。
while循环将捕获的日志行填充到一个本地数组中,每隔一秒钟,该数组的内容就会被传递给一个线程安全的全局日志方法,该方法使用信号量确保没有任何内容被混淆
为了避免来自您的不必要的反馈循环:我当然已经确保(使用调试输出)输出在线程级别上已经混淆了,并且不是输出链中稍后锁定错误的结果
我的问题是:线程A中本地定义的$out FH接收到的输出肯定来自线程B中运行的完全不同的程序,因此应该在线程B中单独定义的线程本地$out FH中结束,这怎么可能呢?我是在这里犯了一个严重的错误,还是仅仅因为perl线程一团糟?最后,建议使用什么方法来正确地分离数据(最好是以某种优雅的方式)
更新:由于流行的需求,我在这里添加了全线程方法:
sub async_command {
my $wname = shift;
my $cmd = shift;
my $acnt = shift;
my $delay = shift;
my $errlog = shift;
my $last = time();
my $pid = 0;
my @log;
my $out;
push(@log, $wname.':::'.$acnt.':::'.$last.':::LOG:::--- thread started ---'.($delay ? ' (sleeping for '.$delay.' seconds)':''));
push(@log, $wname.':::'.$acnt.':::'.$last.':::ERR:::--- thread started ---') if ($errlog);
if ($delay) { sleep($delay); }
# Start worker with output pipe. stdbuf prevents unwanted buffering
# sed tags stdout vs stderr
$pid = open($out, "( stdbuf -oL ".$cmd." | stdbuf -oL sed -e 's/^/:::LOG:::/' ) 2>&1 |") or xlog('async execution failed for: '.$cmd, LOG_LEVEL_NORMAL, LOG_ERROR);
# catch all worker output here
while(<$out>)
{
if( $_ =~ /^:::LOG:::/ )
{
push(@log, $wname.':::'.$acnt.':::'.time().$_);
} else {
push(@log, $wname.':::'.$acnt.':::'.time().':::ERR:::'.$_);
}
if (time() - $last > 1)
{
mlog($acnt, @log);
$last = time();
@log = ();
}
}
close($out);
waitpid($pid, 0);
push(@log, $wname.':::'.$acnt.':::'.time().':::LOG:::--- thread finished ---');
push(@log, $wname.':::'.$acnt.':::'.time().':::ERR:::--- thread finished ---') if ($errlog);
mlog($acnt, @log);
byebye();
}
sub-async\u命令{
我的$wname=班次;
我的$cmd=shift;
我的$acnt=班次;
我的$delay=shift;
我的$errlog=shift;
我的$last=时间();
我的$pid=0;
我的@log;
我的美元用完了;
推送(@log,$wname.::。$acnt.:::。$last.::::---线程已启动--'($delay?'(为“$delay.”秒睡眠):);
推送(@log,$wname.:。$acnt.:::。$last.::::错误::---线程已启动----)如果($errlog);
if($delay){sleep($delay);}
#使用输出管道启动辅助进程。stdbuf可防止不必要的缓冲
#sed标签stdout vs stderr
$pid=open($out,“($cmd.”stdbuf-oL sed-e's/^/:::LOG::/”)2>&1“)或xlog(“:”异步执行失败。$cmd,日志级别正常,日志错误);
#在此处捕获所有工人输出
while()
{
如果($\=~/^:::日志::/)
{
推送(@log,$wname.:'.$acnt.::'.time().$);
}否则{
推送(@log,$wname.:'.$acnt.:'.time().::错误::'.$);
}
如果(时间()-$last>1)
{
mlog($acnt,@log);
$last=time();
@对数=();
}
}
收尾(美元);
waitpid($pid,0);
推送(@log,$wname.:'.$acnt.::'.time().:::日志::---线程已完成--');
如果($errlog),则推送(@log,$wname.':'.$acnt.:'.time().:::错误::---线程已完成--');
mlog($acnt,@log);
拜拜();
}
所以。。。在这里您可以看到@log和$out都是线程局部变量。xlog(全局日志)和mlog方法(工作日志)实际上使用Thread::Queue进行进一步处理。我只是不希望每个线程每秒使用它超过一次,以避免过多的锁定开销
我已将push(@log…语句复制到xlog()中调用调试。由于工作名称$wname与执行的$cmd有一定联系,$acnt是每个线程的唯一数字,因此我清楚地看到,从$out FH读取的日志输出肯定来自与此线程中执行的$cmd不同的$cmd,而$acnt和$wname仍然是真正属于它的我还可以看到,这些日志行不会出现在另一个线程的$out FH上,它们应该出现的位置。我想我们需要更多的细节-你不应该-混淆了
$out
中的行-我想应该看看@log
的作用域在哪里,或者$cmd
实际上在做什么。但是我可以没有更多的代码就不能说明。Perl线程是相当好的,但是我强烈建议对于日志合并操作<代码>线程::队列< /代码>是作业的工具。也请考虑<代码> IPC::Open2(或3)。或者IPC::Run
。请记住,仅仅因为您使用了stdbuf
,并不意味着您没有得到perl输出缓冲。您是说您从一个$out
中获得两个孩子的输出吗?如果是这样,我相信您是错的。请重新评估您如何才能得出这个结论。@Sobrique:根据要求,我增加了更多的细节。我也解释了