2013-02-14 5 views
7

Perl Rookie hier, also bitte sanft sein :)Perl Einfädeln Objektmethode

Ich habe geschrieben Code folgende Spur meiner Hunde zu halten, wenn ich (nicht wirklich) zu jagen. Jedes Mal wenn ein Hund eine Ente findet, signalisiert er dem Hauptfaden, der dann Informationen von jedem der Hunde in der Packung sammelt.

#!/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; 

Erwartete Ausgabe des obigen Code wäre so etwas wie:

Labrador: 1 Enten !, Retriever gefunden: gefundene0 Enten !, Shepherd: gefundene0 Enten!

Labrador: 2 Enten gefunden !, Retriever: 0 Enten gefunden !, Hirte: 0 Enten gefunden!

Labrador: gefunden 3 Enten !, Retriever: gefunden 0 Enten !, Hirte: gefunden 0 Enten!

Labrador: 3 Enten gefunden !, Retriever: 1 Enten gefunden !, Hirte: 0 Enten gefunden!

Labrador: 4 Enten gefunden !, Retriever: 1 Enten gefunden !, Schäferhund: 0 Enten gefunden!

Labrador: 5 Enten gefunden !, Retriever: 1 Enten gefunden !, Hirte: 0 Enten gefunden!

Labrador: gefunden 6 Enten !, Retriever: gefunden 1 Enten !, Hirte: gefunden 0 Enten!

Labrador: gefunden 6 Enten !, Retriever: gefunden 1 Enten !, Hirte: gefunden 0 Enten!

Labrador: gefunden 6 Enten !, Retriever: 1 Enten gefunden !, Schäfer: 1 Enten gefunden!

Statt dessen, was ich erhalte, ist die folgende:

Labrador: 1 gefundene Enten !, Retriever: gefundene0 Enten !, Shepherd: gefundene0 Enten!

Labrador: 2 Enten gefunden !, Retriever: 0 Enten gefunden !, Hirte: 0 Enten gefunden!

Labrador: gefunden 3 Enten !, Retriever: gefunden 0 Enten !, Hirte: gefunden 0 Enten!

Labrador: gefunden 0 Enten !, Retriever: gefunden 1 Enten !, Hirte: gefunden 0 Enten!

Labrador: gefunden 4 Enten !, Retriever: gefunden 0 Enten !, Hirte: gefunden 0 Enten!

Labrador: gefunden 5 Enten !, Retriever: gefunden 0 Enten !, Hirte: gefunden 0 Enten!

Labrador: gefunden 0 Enten !, Retriever: gefunden 2 Enten !, Hirte: gefunden 0 Enten!

Labrador: gefunden 0 Enten !, Retriever: gefunden 0 Enten !, Schäfer: gefunden 1 Enten!

Beachten Sie, wie die Anzahl der Enten jedes Hundes auf Null zurückgesetzt wird, wenn ein anderer Hund spricht.

Gibt es irgendwelche Einsichten darüber, welche Fußnote ich beim Lesen des Llama beschönigt haben muss?

+2

Dies ist eine ziemlich gute Frage für einen Perl-Rookie. :) –

+1

Signale und Threads nicht gut mischen. Sie können nicht einen bestimmten Thread afaik signalisieren. update: scheint das threads doc nicht zu stimmen, zeigt aber mit '$ thr-> kill', nicht einfach kill – ysth

+0

@JonahBishop - danke, schätze ich :) Ungeduldig auf halbem Weg durch das Alpaka, begann ich einen meiner sprichwörtlichen Juckreiz zu kratzen ... schätze, das ist, was ich bekomme, wenn ich außerhalb der Reihe bin :) –

Antwort

7

Das grundlegende Problem besteht darin, dass Perl-Variablen standardmäßig nicht gemeinsam genutzt werden, was mit ein bisschen Seltsamkeit darüber kombiniert, welcher Thread welches Signal bedient, um das Ergebnis zu erzeugen, das Sie sehen.

Wenn Sie Ihre Jagdfäden spawnen, erhält jeder von ihnen eine eigene Kopie von @dogs und seinen Inhalt. So funktionieren Perl-Threads: Der Interpreter und sein aktueller Zustand - @dogs, %SIG, der offene STDOUT - wird komplett geklont. Um zu sehen, wie das funktioniert, sollten Sie diesen Code:

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; 

Das Klonen geschieht bei threads->create Zeit, so dass jedes dieser Themen wird immer eine andere Version von @dogs mit zu nehmen. Als Konsequenz hängt die Liste von Dogs, die bellen, wenn einer von ihnen eine Ente fängt, davon ab, welcher Faden das Signal fängt! (Beachten Sie auch, dass Sie die Reihenfolge entnehmen kann, in dem each passiert den Hash aus dieser Ausgabe zu emittieren.)

Retriever: gefundene0 Enten !, Labrador: 1 gefundene Enten!

Retriever: gefunden 0 Enten !, Labrador: 2 Enten gefunden!

Retriever: 1 Enten gefunden!

Retriever: gefunden 0 Enten !, Labrador: 3 Enten gefunden!

Retriever: gefunden 0 Enten !, Labrador: 4 Enten gefunden!

Retriever: gefunden 0 Enten !, Labrador: gefunden 0 Enten !, Schäfer: gefunden 1 Enten!

Zurück zu Ihrem Code: Wenn der Labrador Threads (Thread 1) aufwacht, aktualisiert er die Labrador ‚s ducksfound und sendet ein SIGUSR1. Jemand (und wir werden mehr über wen in einer Sekunde sprechen) sieht das Signal und barks alle Dogs. Aber die einzige Labrador, die geändert wurde, ist die in Thread 1. Die Retriever und Shepherd Threads (Gewinde 2 und 3) haben das Update auf Labrador 's ducksfound nicht gesehen.

Warum wird der Wert für ducksfound zuerst korrekt gedruckt? Wegen der Art, wie Sie den Signal-Handler installiert haben. Sie haben es prozessweit installiert - erinnern Sie sich daran, dass ich gesagt habe, dass %SIG zu den Dingen gehörte, die in Ihre Threads geklont wurden. So hat jeder der Threads einen Handler für USR1, der alle Dogs zu bark verursacht. Wenn Sie USR1 an $$ senden, fängt der Thread, der gerade in diesem Moment wach ist, es. Und es ist so, dass der Thread, der das Signal gesendet hat, der Thread ist, der wach ist.

Und das erklärt, warum, wenn die Retriever fängt ihre erste Ente, ducksfound Wert ist korrekt, aber die Labrador ist nicht. Retriever fängt die Ente in Thread 2, die SIGUSR1 an sich selbst sendet und dann barks all ihre Dogs. Aber in Thread 2 wurde die Labrador nie aktualisiert, und so zeigt die Bark 0 für Labrador und 1 für Retriever.

Das Problem der ungeteilten Variablen kann um ziemlich einfach durch die Verwendung von threads::shared geholt werden:

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

nun ein, wenn Thread ein Dog aktualisiert, alle Threads wird es sehen und so spielt es keine Rolle, welchen Thread ist Wartung des Signals. Was gut ist, denn in deinem Code bekommt der "Haupt-Thread" (Thread 0) nie die Kontrolle zurück. Dies könnte in Ordnung sein, führt aber wahrscheinlich zu einem etwas seltsameren Verhalten als erwartet.

Wenn Sie tatsächlich dort wollen einen Manager Thread existieren, müssen Sie wahrscheinlich es explizit zum Laichen:

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

Beachten Sie, dass nur in einem Manager-Thread setzen ohne Ihre Dogs teilen in einem Thread würde dazu führen, dass aufwacht um eine Reihe von Nullen zu drucken. Sie müssen beides tun, um die Ergebnisse zu erhalten, die Sie erwartet haben.

+0

> Wenn Sie Ihre Jagdfäden spawnen, erhält jeder von ihnen eine eigene Kopie von @dogs und seinem Inhalt. Was war ich für 'Push @dogs bestimmt, Hund-> new ("Labrador", 1);' eine Instanz einer 'dog' zum Laichen und einen Verweis in den Jäger (Thread Null) speichern. Wie kommt es, dass der Hund von Hunden weiß? Normalerweise würde ich mich einfach hinsetzen und tun, was ich gesagt habe, aber da ich versuche, dieses Zeug zu lernen, finde ich, dass es in Ordnung ist, ein bisschen dicht zu sein ... :) –

+1

Die Art, wie Perl-Threads funktionieren Der vollständige Interpreter wird zusammen mit seinem aktuellen Status geklont. Du erstellst tatsächlich '@ dogs' in Thread 0. Dann klonen die' threads-> create' Aufrufe den gesamten aktuellen Zustand - inklusive '@ dogs' und'% SIG' - in die neuen Threads. Ich glaube, das sind COW-Kopien und daher nicht besonders schwer, aber dieses Verhalten ist der Grund, warum Tutorials in Perl-Threads Ihnen empfehlen, vor dem Laden zu vieler Module oder zu viel Arbeit in Thread 0 zu spawnen. – darch

+0

@ K-spacer Ein Beispiel dazu hinzugefügt die Antwort. – darch

Verwandte Themen