【问题标题】:Perl: How can I edit the structure of a shared_clone?Perl:如何编辑 shared_clone 的结构?
【发布时间】:2021-10-24 16:53:53
【问题描述】:

以下this问题,我面临以下问题:

当我想在几个线程中循环一些哈希并更新它们时,我收到以下错误:

Thread .. terminated abnormally: Invalid value for shared scalar at ...

这是代码:

use feature qw(say);
use strict;
use warnings;
use threads ;
use threads::shared ;
use Data::Dumper qw(Dumper);

my %h1 = (a => 1, b => 2);
my %h2 = (c => 3, d => 4);

my $a1 = \%h1;
my $b1 = \%h2;

my $a1c = shared_clone($a1);
my $b1c = shared_clone($b1);
my $lockvar:shared;

my $nthreads = 3;
for ( 1..$nthreads ) {
    threads->create('job_to_parallelize', $a1c, $b1c, \$lockvar ) ;
}
$_->join() for threads->list();

sub job_to_parallelize {
    my ($a1, $b1, $lockvar) = @_;
    {
        lock $lockvar;        
        $a1->{a}++;
        $b1->{d}++;
        $a1->{scalar}{10} = 1;
    }
}

print Dumper($a1c);
print Dumper($b1c);

据我了解,这是因为在我克隆之前不存在“标量”。 知道如何解决这个问题吗? (即允许我在克隆结构上声明内部线程)。

【问题讨论】:

  • 感谢自动生存,$a1->{scalar}{10} = 1 等同于( ( $a1 //= {} )->{scalar} //= {} )->{10} = 1。这意味着$a1->{scalar}{10} = 1 创建了一个新的(非共享的)散列分配给$a1->{scalar}

标签: multithreading perl clone


【解决方案1】:

正如@Dada 的回答中所解释的,如果您尝试添加对哈希的引用,并且在您克隆哈希时该引用不存在,您也需要将新引用标记为共享。这是另一个如何递归执行此操作的示例:

use feature qw(say);
use strict;
use warnings;
use threads ;
use threads::shared ;
use Data::Dumper qw(Dumper);

my %h1 = (a => 1, b => 2);
my %h2 = (c => 3, d => 4);

my $a1 = \%h1;
my $b1 = \%h2;

my $a1c = shared_clone($a1);
my $b1c = shared_clone($b1);
my $lockvar:shared;

my $nthreads = 3;
for ( 1..$nthreads ) {
    threads->create('job_to_parallelize', $a1c, $b1c, \$lockvar ) ;
}
$_->join() for threads->list();

sub job_to_parallelize {
    my ($a1, $b1, $lockvar) = @_;
    {
        lock $lockvar;
        my $ref = check_exists_key($a1, 'a');
        $$ref++;
        $ref = check_exists_key($b1, 'd');
        $$ref++;
        $ref = check_exists_key($a1, ['scalar', '10']);
        $$ref = 1;
    }
}

sub check_exists_key {
    my ($ref, $key ) = @_;

    if (ref $key eq 'ARRAY') {
        die "Unexpected: key array is empty" if @$key == 0;
        if ( @$key == 1 ) {
            $key = shift @$key;
        }
        else {
            my $k = shift @$key;
            if (exists $ref->{$k}) {
                die "Will not overwrite scalar value with a reference"
                  if $ref->{$k} eq '';
                return check_exists_key( $ref->{$k}, $key);
            }
            if (@$key > 0) {
                my %hash;
                share(%hash);
                $ref->{$k} = \%hash;
                return check_exists_key( $ref->{$k}, $key );
            }
        }
    }
    if ( !exists $ref->{$key} ) {
        $ref->{$key} = undef;
    }
    return \($ref->{$key});
}

print Dumper({a1c => $a1c});
print Dumper({b1c => $b1c});

输出

$VAR1 = {
          'a1c' => {
                     'b' => 2,
                     'a' => 4,
                     'scalar' => {
                                   '10' => 1
                                 }
                   }
        };
$VAR1 = {
          'b1c' => {
                     'c' => 3,
                     'd' => 7
                   }
        };

【讨论】:

    【解决方案2】:

    问题是$a1->{scalar}{10} = 1 创建了一个新的哈希,存储在$a1->{scalar} 中,并且有一个键(10),其值为1。这个新的哈希是不共享的(你从来没有共享过它),因此问题。要解决此问题,您需要共享此新哈希。例如:

    $a1->{scalar} //= shared_clone {} ;
    $a1->{scalar}{10} = 1;
    

    也可以写成:

    ($a1->{scalar} //= shared_clone {} )->{10} = 1;
    

    我知道这有点乏味,尤其是在您要分享大量数据的情况下。如果您不从不同的线程更新相同的密钥,那么您可以在线程内创建一个任意复杂的哈希,并在最后共享它们。例如,

    sub job_to_parallelize {
        my ($a1, $b1, $lockvar) = @_;
        my %thr_data = (key1 => 'data1',
                        key2 => { inside => 42 });
        $a1->{threads->tid} = shared_clone(\%thr_data);
    }
    

    即使您确实更新了线程中的相同键,您也可以使用这种方法并在最后重新组合哈希,例如使用Hash::Merge

    【讨论】:

      猜你喜欢
      • 2021-08-25
      • 1970-01-01
      • 2012-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-08-14
      • 2019-05-01
      相关资源
      最近更新 更多