| |||||
| © Perl Mongers Italia. Tutti i diritti riservati. | |||||
| |||||
Dalla versione 5.6 in poi, Perl propone una nuova implementazione dei thread, chiamati thread dell'interprete o, più sinteticamente ithread. Questa implementazione, ulteriormente migliorata dalla versione 5.8 del linguaggio, prevede che ogni thread sia eseguito nel suo interprete personale: al momento della creazione di un thread, tutti i dati vengono copiati, a meno che non ne sia esplicitamente richiesta la condivisione. Il programma presentato in questo tutorial simula l'assegnazione delle palline in un tennis club. Il club ha un certo quantitativo di palline, ed i giocatori ne richiedono un certo numero a testa. In base ad una policy, il club assegna o meno le palline richieste, oppure ne assegna un numero inferiore. Ogni giocatore e un thread che chiede le palline, gioca per un certo tempo, le restituisce, attende per un certo tempo, e poi le chiede ancora per giocare una nuova partita. Se il club non gli concede le palline, il giocatore aspetta un certo tempo e poi riprova a chiederle. | |||||
| |||||
#!/usr/bin/perl -w
use strict;
use threads;
use threads::shared;
# ### Tennis club ###
# Palline da tennis del club
my $pallinetot = 270;
# Palline correnti
my $palline : shared = $pallinetot;
# ### Giocatori ###
# Nomi
my @nomi = (
'Beppe', 'Michele', 'Laura', 'Marco', 'Elena',
'Mauro', 'Stefania', 'Mirka', 'Agenore', 'Cosimo',
'Alessandro', 'Valentina', 'Denis', 'Sabrina', 'Anna',
'Nicola', 'Luca', 'Frida', 'Gliano', 'Asdrubale',
'Lisa', 'Elisa', 'Louis', 'Ernesto', 'Loris',
);
# Massimo tempo per completare la partita
my $tempomax = 20;
# Tempo di attesa se le palline sono rifiutate per riprovare
my $tempobar = 2;
# Tempo di attesa tra la restituzione delle palline e una nuova partita
my $tempoidl = 3;
# Massimo numero di palline che possono richiedere
my $pallinemaxe = 25;
# ### Programma principale ###
# Crea i thread giocatori
my @g;
for my $i(0..$#nomi) {
$g[$i] = threads->new(\&giocatore, $nomi[$i]);
}
# Attende la fine prima di uscire (non esce mai)
for my $i(0..$#nomi) {
$g[$i]->join();
} | |||||
| |||||
Il programma, dopo aver incluso i moduli threads e threads::shared
(quest'ultimo è necessario per poter condividere variabili tra un
thread e l'altro), definisce alcune variabili iniziali: numero di
palline appartenenti al club; nomi dei giocatori (per rendere possibile
una immediata distinzione visiva di ciasun thread); tempo massimo
a disposizione di ogni giocatore per completare una partita; tempo
di attesa nel caso in cui il club rifiuti di concedere le palline al
giocatore; tempo di attesa tra la restituzione delle palline ed una
nuova richiesta di altre; numero massimo di palline che un singolo
giocatore può richiedere in una volta. Notate inoltre, in questa
sezione, che A questo punto vengono creati i thread corrispondenti ai vari
giocatori, ed i loro handle vengono memorizzati nell'array A thread exited while 26 other threads were still running. Questo indica che tutti i thread dipendenti da quello principale non hanno potuto terminare la loro esecuzione, in quanto il thread principale è uscito. | |||||
| |||||
# ### Subroutine ###
# Policy 1: assegna al giocatore le palline richieste, se disponibili
sub policy1 {
my ($pallineric) = @_;
($palline >= $pallineric) ? return $pallineric : return 0;
}
# Policy 2: se c'e` ancora almeno il 20% delle palline disponibili, assegna
# quelle richieste; se ce ne sono meno, ne assegna al massimo il 2.5%
# ogni volta
sub policy2 {
my ($pallineric) = @_;
if ($palline < int($pallinetot/5)) {
if ($pallineric > int($pallinetot/40)) {
($palline >= int($pallinetot/40)) ? return int($pallinetot/40) : return 0;
} else {
($palline >= $pallineric) ? return $pallineric : return 0;
}
} else {
($palline >= $pallineric) ? return $pallineric : return 0;
}
}
# Richiesta palline
sub chiedi_palline {
my ($nome, $pallineric) = @_;
# Ora $palline E<egrave> tutta nostra
lock ($palline);
# Assegna le palline secondo la policy desiderata
my $pallineass = &policy2($pallineric);
if ($pallineass > 0) {
$palline -= $pallineass;
print "$pallineass palline (su $pallineric richieste) concesse a $nome (totale $palline)\n";
return $pallineass;
} else {
print "$pallineric palline rifiutate a $nome (totale $palline)\n";
return 0;
}
}
# Restituzione palline
sub restituisci_palline {
my ($nome, $pallineric) = @_;
lock ($palline);
$palline += $pallineric;
print "$pallineric palline restituite da $nome (totale $palline)\n";
}
# Giocatore
sub giocatore {
my ($nome) = @_;
# Il giocatore gioca e rigioca di continuo
while (1) {
# Richiedi un numero di palline tra 1 e $pallinemaxe
my $pallineric = int(rand($pallinemaxe))+1;
my $pallineass;
# Chiedi le palline
while (($pallineass = &chiedi_palline($nome, $pallineric)) == 0) {
sleep $tempobar;
}
sleep int(rand($tempomax))+1;
&restituisci_palline($nome, $pallineass);
sleep $tempoidl;
}
} | |||||
| |||||
Analizziamo ora le operazioni compiute da ciascun singolo
thread/giocatore. È opportuno ricordare che, al momento della
creazione di un thread, tutti i dati (scalari, array, funzioni, e
quant'altro) presenti nel programma in quell'istante vengono
clonati: di fatto viene creato un programma uguale all'originale,
che gira come un thread dipendente dal programma principale.
Dunque, tutti i dati sono separati, fatta eccezione per quelli
esplicitamente condivisi. Nel nostro caso viene condivisa solo
la variabile La subroutine che costituisce il codice principale di ciascun
thread è Il clou del programma risiede nella funzione $palline -= $pallineass L'operatore svolge fondamentalmente due operazioni su THREAD1: legge $palline THREAD2: legge $palline THREAD1: decrementa $palline => 23 THREAD2: decrementa $palline => 22 Alla fine lock ($palline); Con la chiamata a Ottenuto il lock, le palline vengono assegnate in base ad una policy.
Nel programma sono incluse due funzioni, La funzione | |||||
| |||||
Numero massimo di partite Si potrebbe definire una variabile di configurazione che indichi
il numero massimo di partite che ogni thread/giocatore può fare.
Al momento della creazione del thread (cioè all'inizio della sub
Coda per giocatori in attesa Al momento, se le palline non sono disponibili, un giocatore attende un tempo predeterminato e poi ritorna a chiederle. Questo sistema non garantisce che il primo giocatore a cui sono negate sia il primo a cui saranno poi concesse. Si potrebbe dunque prevedere una coda, in cui vengono inseriti i giocatori a cui vengono rifiutate le palline, nell'ordine in cui essi si sono presentati a chiederle. Quando un giocatore viene/ritorna a chiedere le palline, viene controllata la sua posizione sulla coda e, in caso ci siano altri davanti a lui, gli vengono negate, oppure gli viene assegnato un numero di palline tale che quelli in coda davanti a lui non si trovino senza al loro ritorno. Possibilità, per un thread/giocatore, di rifiutare le palline Se il club, per qualche motivo, deve concedere un numero di palline inferiore a quello richiesto dal giocatore, si può prevedere che quest'ultimo le rifiuti, in quanto non sufficienti per permettergli di completare una partita. In questo caso sarebbe probabilmente necessari definire un numero di palline minimo richiesto da ciascun giocatore. | |||||