Multithreading perl多线程:使用“捕获子线程子线程的stdio”;混合;结果

Multithreading perl多线程:使用“捕获子线程子线程的stdio”;混合;结果,multithreading,perl,pipe,stdout,filehandle,Multithreading,Perl,Pipe,Stdout,Filehandle,我用perl编写了一个大规模多线程应用程序,它基本上是扫描文件或目录结构的更改(使用inotify或轮询)。当它检测到更改时,会启动子线程,根据配置执行以更改的文件作为参数的程序 到目前为止,这工作得相当不错,除了我的应用程序还尝试捕获外部执行程序的stdout和stderr,并以结构化方式将它们写入日志文件 然而,我在这里遇到了偶尔但严重的输出混乱,每次线程a上的程序的标准输出(当然,通常是在繁重的工作负载下,正常的测试总是运行良好)都会同时进入线程B上运行的另一个程序的标准输出管道FH 运行

我用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:根据要求,我增加了更多的细节。我也解释了