2013-02-14 20 views
7

Perlのルーキーは、とても優しくしてください:)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頭のアヒルを見つけました!、シェパード:アヒルを見つけました!

ラブラドール:6頭のアヒルを見つけました!、レトリーバー:1頭のアヒルを見つけました!、シェパード:アヒルを見つけました!

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

はその代わり、私が取得することは以下の通りです:

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

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

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

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

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

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

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

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

他の犬が話している間、どの犬の鴨の数もゼロにリセットされていることに注意してください。

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

+2

これはPerl新人にとって非常に大きな質問です。 :) –

+1

信号とスレッドはうまく混合されません。あなたは特定のスレッドafaikを通知することはできません。アップデート:スレッドのドキュメントには同意していないようですが、普通のkillではなく '$ thr-> kill'を使って表示されます – ysth

+0

@JonahBishop - ありがとう、私は推測しています:)途中でアルパカの途中で困ったことがあります。これは私が順番にコーディングするために得たものです: –

答えて

7

基本的な問題は、デフォルトでPerl変数が共有されていないということです。これは、どのスレッドがどのシグナルを処理しているかについての不思議さと結合しています。

あなたのハンティングスレッドをスポーンすると、それぞれが@dogsとその内容のコピーを取得します。これはPerlスレッドの動作方法です:インタプリタとその現在の状態 - @dogs%SIG、オープン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羽のアヒルを見つけました!

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

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

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

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

レトリーバー:アヒルが見つかりました!、ラブラドール:アヒルが見つかった!、シェパード:アヒルが1つ見つかりました!戻るあなたのコードに

Labradorスレッド(スレッド1)が目覚めるとき、それはLabradorducksfoundを更新し、SIGUSR1を送信します。誰か(そして2番目の人についてはもっと詳しく話します)とbarksのすべてがDogsです。しかし、変更されたLabradorはスレッド1のものだけです。RetrieverShepherdスレッド(それぞれスレッド2とスレッド3)はLabradorducksfoundへの更新を見ていません。

ducksfoundの値が最初に正しく印刷されるのはなぜですか?シグナルハンドラをインストールしたからです。あなたはそれをプロセス全体にインストールしました。私は%SIGがあなたのスレッドにクローン化されたものの中にあったと思い出しました。そのため、各スレッドにはのハンドラがあり、すべてがDogsbarkになります。 USR1$$に送信すると、その時点でスレッドが目を覚ましていても、それをキャッチします。そして、信号を送信したスレッドが目を覚ましているスレッドになります。

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

非共有変数の問題は、単にthreads::sharedを使用することにより、かなりの周りに得することができます。

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

1つのスレッドがDogを更新したときに今、すべてのスレッドがそれを見ることができますので、どのスレッドもかまいません。信号を処理する。あなたのコードでは、 "メインスレッド"(スレッド0)が制御権を返さないため、どちらが良いですか。これは大丈夫かもしれませんが、予想よりも少し奇妙な行動につながる可能性があります。あなたが実際マネージャスレッドを存在するがしたい場合は

、あなたはおそらく、明示的にそれを起動する必要があります。ちょうど目覚めるスレッドにつながるあなたのDogsを共有することなくマネージャスレッドに入れ

# 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)); 

注意をたくさんのゼロを印刷する。期待していた結果を得るには、両方を行う必要があります。

+0

>あなたの狩猟用のスレッドをスポーンすると、それぞれが@dogsとその内容のコピーを取得します。 ' は、いくつかの' Dog'のインスタンスを起動し、ハンター(スレッドゼロ)内の参照を保存するために、私が意図したもの は新しい 'プッシュ@dogs、Dog->(「ラブラドール」、1)のためでした。 「犬」はどのように「@犬」について知っていますか? 通常、私は言われたように座るだけですが、私がこのことを学びたいと思っているのを見て、ちょっと密度が高いのは間違いないと思います。:) –

+1

Perlスレッドの仕組み、インタプリタ全体が現在の状態と共に複製されます。あなたは本当にスレッド0に '@ dogs'を作成します。次に' threads-> create'は '@ dogs'と'%SIG'を含む現在の状態のすべてを新しいスレッドにクローンします。私はこれらがCOWのコピーであると信じていますが、ひどく重くはありませんが、Perlのスレッドに関するチュートリアルでは、あまりにも多くのモジュールをロードする前に生成するか、スレッド0であまりにも多くの作業をすることを勧めています。 – darch

+0

@ K-答え。 – darch

関連する問題