8

ここにPerlの新人がいるので、優しくしてください:)

私は狩りをしているときに犬を追跡するために次のコードを書きました(実際にはそうではありません)。犬がアヒルを見つけるたびに、メインスレッドに信号を送り、メインスレッドはパック内の各犬から情報を収集します。

#!/usr/bin/env perl

use strict;
use warnings;
use v5.14;

use threads;

{
    package Dog;

    sub new {
        my ($class, $name, $dt) = @_;
        my $self = {
            dt => $dt,      # will find a duck every $dt seconds
            name => $name,
            ducksfound => 0
        };
        bless $self, $class;
    }

    sub hunt {
        #
        # the "thread" method -- the dog will hang around for $dt seconds,
        # then alert the main thread by sending SIGUSR1
        #
        my $self = shift;
        while (1) {
            sleep $self->{dt};
            $self->{ducksfound} += 1;
            kill USR1 => $$;
        }
    }

    sub bark {
        my $self = shift;
        sprintf "%s: found %d ducks!", ($self->{name}, $self->{ducksfound});
    }

    1;
}

my @dogs;

$SIG{USR1} = sub {
    say join ", ", map { $_->bark } @dogs;
};


push @dogs, Dog->new("Labrador", 1);
push @dogs, Dog->new("Retriever", 2);
push @dogs, Dog->new("Shepherd", 3);

threads->create( sub { $_->hunt } ) for @dogs;
$_->join for threads->list;

上記のコードの期待される出力は次のようになります。

ラブラドール:1羽のアヒルが見つかりました!、レトリバー:0羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:2羽のアヒルが見つかりました!、レトリバー:0羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:3羽のアヒルが見つかりました!、レトリバー:0羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:3羽のアヒルが見つかりました!、レトリバー:1羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:4羽のアヒルが見つかりました!、レトリバー:1羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:5羽のアヒルが見つかりました!、レトリバー:1羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:6羽のアヒルが見つかりました!、レトリバー:1羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:6羽のアヒルが見つかりました!、レトリバー:1羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:6羽のアヒルが見つかりました!、レトリバー:1羽のアヒルが見つかりました!、シェパード:1羽のアヒルが見つかりました!

代わりに、私が得るものは次のとおりです。

ラブラドール:1羽のアヒルが見つかりました!、レトリバー:0羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:2羽のアヒルが見つかりました!、レトリバー:0羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:3羽のアヒルが見つかりました!、レトリバー:0羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:アヒルが0匹見つかりました!、レトリバー:アヒルが1匹見つかりました!、シェパード:アヒルが0匹見つかりました!

ラブラドール:4羽のアヒルが見つかりました!、レトリバー:0羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:5羽のアヒルが見つかりました!、レトリバー:0羽のアヒルが見つかりました!、シェパード:0羽のアヒルが見つかりました!

ラブラドール:アヒルが0匹見つかりました!、レトリバー:アヒルが2匹見つかりました!、シェパード:アヒルが0匹見つかりました!

ラブラドール:アヒルが0匹見つかりました!、レトリバー:アヒルが0匹見つかりました!、シェパード:アヒルが1匹見つかりました!

他の犬が話しているときに、すべての犬のアヒルの数がゼロにリセットされることに注目してください。

ラマを読んでいる間、私がどの特定の脚注に目を通したに違いないかについての洞察はありますか?

4

1 に答える 1

8

基本的な問題は、Perl変数がデフォルトで共有されていないことです。これは、どのスレッドがどのシグナルを処理して、表示されている結果を生成するかについての少し奇妙なことと組み合わされます。

ハンティングスレッドを生成すると、それぞれが独自のコピー@dogsとその内容を取得します。これがPerlスレッドの動作方法です。インタプリタとその現在の状態( @dogs、、%SIGopen STDOUT)は完全に複製されます。それがどのように機能するかを確認するには、次のコードを検討してください。

my %dog_decls = (
    Labrador    => 1,
    Retriever   => 2,
    Shepherd    => 3,
);

while (my ($name, $delay) = each %dog_decls) {
    my $dog = Dog->new($name, $delay);
    push @dogs, $dog;
    threads->create(sub { $dog->hunt });
}

$_->join for threads->list;

クローン作成はthreads->create一度に行われるため、これらのスレッドはそれぞれ異なるバージョン@dogsを取得しています。結果として、Dogsそれらの1つがアヒルを捕まえたときのその樹皮のリストは、どのスレッドが信号を捕らえたかによって異なります。each(また、この出力からハッシュを放出した順序を推測できることにも注意してください。)

レトリバー:0羽のアヒルが見つかりました!、ラブラドール:1羽のアヒルが見つかりました!

レトリバー:0羽のアヒルが見つかりました!、ラブラドール:2羽のアヒルが見つかりました!

レトリバー:1羽のアヒルが見つかりました!

レトリバー:0羽のアヒルが見つかりました!、ラブラドール:3羽のアヒルが見つかりました!

レトリバー:0羽のアヒルが見つかりました!、ラブラドール:4羽のアヒルが見つかりました!

レトリバー:アヒルが0匹見つかりました!、ラブラドール:アヒルが0匹見つかりました!、シェパード:アヒルが1匹見つかりました!

コードに戻る:Labradorスレッド(スレッド1)がウェイクアップすると、が更新され、がLabrador送信ducksfoundされますSIGUSR1。誰か(そして誰についてはすぐに話します)が信号とbarksすべてを見るのDogsです。ただし、Labrador変更されたのはスレッド1のスレッドRetrieverだけです。Shepherdスレッド(それぞれスレッド2と3)はLabrador'sへの更新を確認していませんducksfound

では、なぜ最初にの値がducksfound正しく印刷されるのですか?シグナルハンドラーのインストール方法が原因です。%SIGプロセス全体にインストールしました。スレッドに複製されたものの1つであると言ったことを思い出してください。したがって、各スレッドには、すべてのを引き起こすハンドラーがありUSR1ます。に送信すると、その時点でアウェイクしているスレッドがキャッチされます。そして、シグナルを送信したスレッドがアウェイクしているスレッドであることが起こります。DogsbarkUSR1$$

Retrieverそしてそれは、キャッチが最初のアヒルを捕まえるとき、そのducksfound値は正しいが、Labrador'sは正しくない 理由を説明しています。Retrieverスレッド2でアヒルを捕まえ、SIGUSR1それ自体に送信し、次にbarksそのすべてを送信しますDogs。しかし、スレッド2では、Labradorは更新されていないため、樹皮は0を示しLabrador、1を示しRetrieverます。

非共有変数の問題は、次を使用するだけでかなり簡単に回避できますthreads::shared

use threads::shared;
...
my @dogs :shared;
...
push @dogs, shared_clone(Dog->new("Labrador",  1));

これで、1つのスレッドがを更新するDogと、すべてのスレッドがそれを認識できるため、どのスレッドがシグナルを処理しているかは関係ありません。あなたのコードでは「メインスレッド」(スレッド0)が制御を取り戻すことは決してないので、これは良いことです。これは問題ないかもしれませんが、おそらくあなたが期待するよりも少し奇妙な振る舞いにつながるでしょう。

マネージャースレッドを実際に存在させたい場合は、おそらく明示的にスポーンする必要があります。

# in Dog::new
        my ($class, $name, $hunter, $dt) = @_;
        ...
        hunter => $hunter,
# in Dog::hunt
        $self->{hunter}->kill('USR1');
# in main
my $hunter_thread = threads->create(
    sub {
        local $SIG{USR1} = sub {
            say join ", ", map { $_->bark } @dogs;
        };
        while (1) { usleep 100_000 } # higher resolution than hunt events
    }
);
...
push @dogs, shared_clone(Dog->new("Labrador", $hunter_thread, 1));

共有せずにマネージャースレッドを挿入するだけDogsで、スレッドがウェイクアップしてゼロの束を出力することに注意してください。期待した結果を得るには、両方を行う必要があります。

于 2013-02-14T17:28:23.653 に答える