【问题标题】:Perl seg fault while joining threads加入线程时出现 Perl 段错误
【发布时间】:2015-07-08 16:53:56
【问题描述】:

我有一个类似于下面的代码。我有一个主脚本正在调用另一个名为 initial.pm 的模块。 initial.pm 打开与 AMQP 服务器(在我的情况下为 RabbitMQ)的连接,并使用 Net::AMQP::RabbitMQ 库建立连接。一切正常,除了当我尝试加入我的线程时,我得到 segmentation fault

我认为 Net::AMQP::RabbitMQ 不是线程安全的。但这仅由主线程使用。如果您只是复制并粘贴下面的代码,我很确定您可以重现该错误。

我该如何解决?

ma​​in.pl

#!/usr/bin/perl
use Cwd qw/realpath/;
use File::Basename qw/dirname/;
use lib 'lib';
use threads;
use threads::shared;
use initial;

my @threads = ();
my $run :shared = 1;

my $init = load initial($name);

$SIG{'TERM'} = sub {
    $run = 0;
};

threads->create(\&proc1);
threads->create(\&proc2);


while($run){
    sleep(1);
    print "I am main thread\n";
}

$_->join() for threads->list();

sub proc1 {
    while($run){
        sleep(1);
        print "I am child thread 1 \n"
    }
}

sub proc2 {
    while($run){
       sleep(1);
       print "I am child thread 2 \n";
    }
}

lib/initial.pm

package initial;

use Net::AMQP::RabbitMQ;
use Cwd qw/realpath/;
use File::Basename qw/dirname/;

my $mq;
my $stop = 0;

sub load {
    my $class = shift;
    my $self = {};
    connectq();
    bless $self,$class;
    return $self;
}

sub connectq {
    $mq = Net::AMQP::RabbitMQ->new();
    my ($host,$port,$user,$pass) = ('localhost','5672','guest','guest');
    $mq->connect($host, {
        user => $user,
        password => $pass,
        port => $port,
        timeout => 10,
    });

    $mq->channel_open(1);
    $mq->consume(1, 'logger');
}

1;

【问题讨论】:

  • Perl 线程很重。每个线程都有自己的$init 变量副本,并且initial 类的线程安全性将是相关的。在创建两个线程之后,您是否有更好的运气来分配 $init 分配?
  • 为什么将join 移回信号处理程序。这没有任何意义。
  • 显而易见的猜测是 Net::AMQP::RabbitMQ 不是线程安全的
  • 我更新了代码,请立即查看。有同样的问题。是 Net::AMQP::RabbitMQ 不是线程安全的。有什么解决办法吗?
  • @mob 如果我在创建线程后调用初始类,我看不到错误。但这只是更大代码的一部分,并且在创建线程后每次调用类都不是我的解决方案。有没有办法告诉 perl 不要复制我的 $init 变量?

标签: multithreading perl


【解决方案1】:

我无法直接重现您的问题,因为我没有安装该库。

在非线程安全模块中“伪造”线程安全的一种方法是将您的“使用”范围重新调整到您将使用它的位置。

您会看到,当您启动一个线程时,它会复制程序状态 - 加载的库和所有内容。

如果你的跑步(类似):

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig;
use Data::Dumper;

sub thread1 {
    print threads->self->tid.": Includes:", Dumper \%INC,"\n";
}


#main;

print "Main includes:", Dumper \%INC,"\n";
threads -> create ( \&thread1 );

您会看到XML::Twig 已加载到两者中。如果“加载”模块的过程会导致一些状态更改(并且它可以),那么您会立即遇到潜在的线程安全问题。

但是,如果您改为这样做:

#!/usr/bin/env perl

use strict;
use warnings;

use threads;
use Data::Dumper;

sub thread1 {
    require XML::Twig;
    XML::Twig -> import;
    print threads->self->tid.": Includes:", Dumper (\%INC),"\n";
}


#main;

print "Main includes:", Dumper (\%INC),"\n";
threads -> create ( \&thread1 );
foreach my $thr ( threads -> list ) { 
   $thr -> join;
}

有效地导致模块在线程中动态加载 - 该模块仅存在于一个“代码实例”中,因此您被“线程安全”问题绊倒的可能性要小得多.

或者 - forking 而不是 threading ... 可能是另一种选择。这有稍微不同的“安全”问题。

但确实没有办法避免这种情况。即使使用共享变量,核心问题是 - 当你线程时,代码位以不同的顺序发生。结果可能会发生各种果味的事情。共享变量是确保每次检查 same 变量的一种方法 - 例如分享$init,但这很可能会让事情变得更糟,因为你可能会用不同的线程践踏相同的实例/套接字。

但是,您可以将“线程安全”问题减少到有限的范围,并使用例如Thread::Queue 向/从您的“模块用户”线程传递消息。

【讨论】:

  • 锁定可以解决吗?
  • 理论上是的。在实践中,您必须拆除模块并找到问题的根源,并在其周围放置一个警卫。可能有多个。不是不可能,但也不容易。
猜你喜欢
  • 2020-04-09
  • 2014-12-12
  • 1970-01-01
  • 1970-01-01
  • 2011-06-29
  • 2016-10-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多