2013-02-14 13 views
7

Perl rookie qui, quindi cerca di essere gentile :)Perl filettatura del metodo oggetto

Ho scritto seguente codice per tenere traccia dei miei cani quando sono in caccia (non proprio). Ogni volta che un cane trova un'anatra, segnala il filo principale, che quindi raccoglie informazioni da ciascuno dei cani nel mazzo.

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

uscita prevista del codice di cui sopra sarebbe qualcosa di simile:

Labrador: trovati 1 anatre !, Retriever: trovati 0 anatre !, Pastore: ha trovato 0 anatre!

Labrador: trovato 2 anatre !, Retriever: trovato 0 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 3 anatre !, Retriever: trovato 0 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 3 anatre !, Retriever: trovato 1 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 4 anatre !, Retriever: trovato 1 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 5 anatre !, Retriever: trovato 1 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 6 anatre !, Retriever: trovato 1 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 6 anatre !, Retriever: trovato 1 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 6 anatre !, Retriever: trovato 1 anatre !, Pastore: trovato 1 anatre!

Invece, quello che ottengo è il seguente:

Labrador: trovati 1 anatre !, Retriever: trovati 0 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 2 anatre !, Retriever: trovato 0 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 3 anatre !, Retriever: trovato 0 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 0 anatre !, Retriever: trovato 1 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 4 anatre !, Retriever: trovato 0 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 5 anatre !, Retriever: trovato 0 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 0 anatre !, Retriever: trovato 2 anatre !, Pastore: trovato 0 anatre!

Labrador: trovato 0 anatre !, Retriever: trovato 0 anatre !, Pastore: trovato 1 anatre!

Nota come il numero di anatre di ogni cane si azzera a zero, un altro cane sta parlando.

Qualche intuizione su quale nota in particolare devo aver sorvolato mentre leggevo il Lama?

+2

Questa è una bella domanda per un debuttante Perl. :) –

+1

segnali e thread non si combinano bene. non è possibile segnalare un particolare thread afaik. aggiornamento: sembra che il thread doc non sia d'accordo, ma mostra l'uso di '$ thr-> kill', non plain kill – ysth

+0

@JonahBishop - grazie, suppongo :) Impaziente a metà strada attraverso l'Alpaca, ho iniziato a grattarmi uno dei miei proverbiali pruriti ... Immagino che questo è ciò che ottengo per la codifica fuori turno :) –

risposta

7

Il problema fondamentale è che le variabili Perl non sono condivise di default, il che si combina con un po 'di stranezza su quale thread sta servendo quale segnale per produrre il risultato che si sta vedendo.

Quando si generano i thread di ricerca, ognuno di essi riceve la propria copia di @dogs e il suo contenuto. Questo è il modo in cui i thread Perl funzionano: l'interprete e il suo stato corrente - @dogs, %SIG, l'open STDOUT - è clonato intero. Per vedere come funziona, si consideri questo codice:

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; 

La clonazione avviene a threads->create tempo, così ognuno di questi fili è sempre una versione diversa di @dogs per prendere con esso. Di conseguenza, l'elenco di Dogs che abbaia quando uno di loro cattura un'anatra dipende da quale filo cattura il segnale! (Anche notare che è possibile dedurre l'ordine in cui each accaduto a emettere l'hash da questa uscita.)

Retriever: trovati 0 anatre !, Labrador: trovati 1 anatre!

Retriever: trovato 0 anatre !, Labrador: trovato 2 anatre!

Retriever: trovato 1 anatre!

Retriever: trovato 0 anatre !, Labrador: trovato 3 anatre!

Retriever: trovato 0 anatre !, Labrador: trovato 4 anatre!

Retriever: trovato 0 anatre !, Labrador: trovato 0 anatre !, Pastore: trovato 1 anatre!

Torna al codice: Quando il Labrador filo (filo 1) si sveglia, si aggiorna il s ducksfoundLabrador' e invia un SIGUSR1. Qualcuno (e parleremo di più su chi in un secondo) vede il segnale e barks tutti gli Dogs. Ma l'unico Labrador che è stato modificato è quello in thread 1. I thread Retriever e Shepherd (thread 2 e 3 rispettivamente) non hanno visto l'aggiornamento a Labrador di ducksfound.

Perché allora il valore per ducksfound viene stampato correttamente in un primo momento? A causa del modo in cui hai installato il gestore di segnale. L'hai installato a livello di processo - ricorda che ho detto che lo %SIG era tra le cose clonate ai tuoi thread. Quindi ognuno dei thread ha un gestore per USR1 che causa tutto il Dogs a bark. Quando si invia USR1 a $$, in qualsiasi momento il thread si svegli in quel momento. E così accade che il thread che ha inviato il segnale sia il thread che è sveglio.

E questo spiega perché quando lo Retriever cattura la sua prima anatra, il suo valore ducksfound è corretto ma non lo è lo Labrador. Retriever cattura l'anatra nella filettatura 2, che invia SIGUSR1 a se stessa e quindi a barks tutti i suoi Dogs. Ma nel thread 2, lo Labrador non è mai stato aggiornato e quindi la corteccia mostra 0 per Labrador e 1 per Retriever.

Il problema delle variabili non condivisi può essere ottenuto in giro abbastanza semplicemente con l'uso di threads::shared:

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

Ora, quando un thread aggiorna un Dog, tutte le discussioni lo vedranno e quindi non importa quale thread è manutenzione del segnale. Il che è buono, perché nel tuo codice il "thread principale" (thread 0) non ha mai più il controllo. Questo potrebbe andar bene, ma probabilmente porta a un comportamento leggermente più strano di quello che ti aspetti.

Se si vuole realmente lì ad esistere un filo manager, probabilmente è necessario per deporre le uova in modo esplicito:

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

Si noti che solo mettendo in un thread manager senza condividere il tuo Dogs si tradurrebbe in un filo che si sveglia per stampare un sacco di zeri. Devi fare entrambe le cose per ottenere i risultati che ti aspettavi.

+0

> Quando si generano i thread di ricerca, ognuno di essi riceve la propria copia di @dogs e il suo contenuto. Quello che intendevo era per '@dogs spinta, cane-> Nuovo ("Labrador", 1);' a generare un'istanza di alcuni 'Dog' e salvare un riferimento nel cacciatore (filo zero). Come mai il 'Cane' sa di' @ cani'? Normalmente vorrei solo sedermi e fare come mi è stato detto, ma visto che sto cercando di imparare questa roba, immagino sia giusto essere un po 'denso ... :) –

+1

Il modo in cui i thread Perl funzionano, il l'interprete intero è clonato insieme al suo stato attuale. In effetti crei '@ dogs' nel thread 0. Quindi i' threads-> create' chiamano clone tutto lo stato corrente - inclusi '@ dogs' e'% SIG' - nei nuovi thread. Credo che queste siano copie COW e quindi non eccessivamente pesanti, ma questo comportamento è il motivo per cui le esercitazioni sui thread Perl ti consigliano di spawn prima di caricare troppi moduli o di fare troppo lavoro nel thread 0. – darch

+0

@ K-spacer Aggiunto un esempio a riguardo la risposta. – darch