Il s'agit de la commande perlthrtut qui peut être exécutée dans le fournisseur d'hébergement gratuit OnWorks en utilisant l'un de nos multiples postes de travail en ligne gratuits tels que Ubuntu Online, Fedora Online, l'émulateur en ligne Windows ou l'émulateur en ligne MAC OS
PROGRAMME:
Nom
perlthrtut - Tutoriel sur les threads en Perl
DESCRIPTION
Ce tutoriel décrit l'utilisation des threads d'interprétation Perl (parfois appelés
fils). Dans ce modèle, chaque thread s'exécute dans son propre interpréteur Perl et toutes les données
le partage entre les threads doit être explicite. L'interface de niveau utilisateur pour fils utilise l'
classe de fils.
REMARQUE: Il y avait une autre version de thread Perl plus ancienne appelée le modèle 5.005 qui utilisait le
classe de fils. Cet ancien modèle était connu pour avoir des problèmes, est obsolète et a été supprimé
pour la version 5.10. Vous êtes fortement encouragé à migrer tout code de threads 5.005 existant
au nouveau modèle dès que possible.
Vous pouvez voir quelle version de thread (ou aucune) vous avez en exécutant "perl -V" et en regardant
dans la rubrique "Plateforme". Si vous avez "useithreads=define" vous avez des threads, si vous
avoir "use5005threads=define" vous avez 5.005 threads. Si vous n'avez ni l'un ni l'autre, vous n'avez pas
tout support de thread intégré. Si vous avez les deux, vous avez des problèmes.
Les threads et threads::shared modules sont inclus dans la distribution principale de Perl.
De plus, ils sont maintenus en tant que modules séparés sur CPAN, vous pouvez donc y vérifier
pour toutes les mises à jour.
Organisateur Ce que Is A Fil à coudre Quoi qu'il en soit?
Un thread est un flux de contrôle à travers un programme avec un seul point d'exécution.
Cela ressemble beaucoup à un processus, n'est-ce pas ? Eh bien, ça devrait. Les fils sont l'un des
morceaux d'un processus. Chaque processus a au moins un thread et, jusqu'à présent, chaque
processus exécutant Perl n'avait qu'un seul thread. Avec 5.8, cependant, vous pouvez créer des threads supplémentaires.
Nous allons vous montrer comment, quand et pourquoi.
Fileté Programme Modèles
Il existe trois manières de base de structurer un programme threadé. Quel modèle vous
choisir dépend de ce que vous voulez que votre programme fasse. Pour de nombreux filetages non triviaux
programmes, vous devrez choisir différents modèles pour différentes parties de votre programme.
Patron/Ouvrier
Le modèle patron/ouvrier en a généralement un patron fil et un ou plusieurs travailleur fils. le
Le fil patron rassemble ou génère les tâches qui doivent être effectuées, puis répartit ces tâches
au thread de travail approprié.
Ce modèle est courant dans les programmes d'interface graphique et de serveur, où un thread principal attend un événement
puis transmet cet événement aux threads de travail appropriés pour traitement. Une fois la
l'événement a été transmis, le thread du patron recommence à attendre un autre événement.
Le fil de bossage fait relativement peu de travail. Alors que les tâches ne sont pas nécessairement effectuées
plus rapide qu'avec toute autre méthode, elle a tendance à avoir les meilleurs temps de réponse de l'utilisateur.
Solution Équipage
Dans le modèle d'équipe de travail, plusieurs threads sont créés qui font essentiellement la même chose pour
différentes données. Il reflète étroitement le traitement parallèle classique et le vecteur
processeurs, où un large éventail de processeurs font exactement la même chose à de nombreux morceaux de
revendre.
Ce modèle est particulièrement utile si le système exécutant le programme distribuera
plusieurs threads sur différents processeurs. Il peut également être utile en lancer de rayons ou
moteurs de rendu, où les threads individuels peuvent transmettre des résultats intermédiaires pour donner le
retour visuel de l'utilisateur.
Pipeline
Le modèle de pipeline divise une tâche en une série d'étapes et transmet les résultats d'une
passez au thread traitant le suivant. Chaque fil fait une chose à chaque morceau de
data et transmet les résultats au thread suivant en ligne.
Ce modèle est le plus logique si vous avez plusieurs processeurs, donc deux threads ou plus
s'exécutera en parallèle, même si cela peut souvent avoir du sens dans d'autres contextes également.
Il tend à garder les tâches individuelles petites et simples, tout en permettant à certaines parties de
le pipeline à bloquer (sur les E/S ou les appels système, par exemple) pendant que les autres parties continuent.
Si vous exécutez différentes parties du pipeline sur différents processeurs, vous pouvez également
profiter des caches sur chaque processeur.
Ce modèle est également pratique pour une forme de programmation récursive où, plutôt que d'avoir un
appel de sous-programme lui-même, il crée à la place un autre thread. Générateurs Prime et Fibonacci
les deux correspondent bien à cette forme de modèle de pipeline. (Une version d'un générateur de nombres premiers
est présenté plus loin.)
Organisateur Ce que mots of discussions sommes-nous Perl fils?
Si vous avez de l'expérience avec d'autres implémentations de threads, vous constaterez peut-être que les choses
ne sont pas tout à fait ce que vous attendez. Il est très important de se rappeler lorsqu'on traite avec Perl
des fils qui Perl Threads Emplacements Pas X Threads pour toutes les valeurs de X. Ils ne sont pas POSIX
threads, ou DecThreads, ou les threads verts de Java, ou les threads Win32. Il y a
similitudes et les concepts généraux sont les mêmes, mais si vous commencez à chercher
détails de mise en œuvre, vous serez soit déçu, soit confus. Peut-être les deux.
Cela ne veut pas dire que les threads Perl sont complètement différents de tout ce qui existe
Viens avant. Ils ne sont pas. Le modèle de thread de Perl doit beaucoup aux autres modèles de thread,
surtout POSIX. Tout comme Perl n'est pas C, cependant, les threads Perl ne sont pas des threads POSIX. Donc
si vous vous trouvez à la recherche de mutex ou de priorités de thread, il est temps de prendre du recul
peu et réfléchissez à ce que vous voulez faire et comment Perl peut le faire.
Cependant, il est important de se rappeler que les threads Perl ne peuvent pas faire les choses comme par magie à moins que
les threads de votre système d'exploitation le permettent. Donc, si votre système bloque l'ensemble du processus sur
"sleep()", Perl le fera généralement aussi.
Perl Threads Emplacements Différent.
Sans fil Modules
L'ajout de threads a considérablement modifié les composants internes de Perl. Il y a des implications
pour les personnes qui écrivent des modules avec du code XS ou des bibliothèques externes. Cependant, puisque les données Perl
n'est pas partagé entre les threads par défaut, les modules Perl ont de grandes chances d'être thread-
sûr ou peut être rendu sans fil facilement. Les modules qui ne sont pas marqués comme thread-safe doivent
être testé ou revu le code avant d'être utilisé dans le code de production.
Tous les modules que vous pouvez utiliser ne sont pas thread-safe, et vous devez toujours supposer qu'un module
est dangereux à moins que la documentation n'indique le contraire. Cela inclut les modules qui sont
distribué dans le cadre du noyau. Les threads sont une fonctionnalité relativement nouvelle, et même certains
les modules standard ne sont pas thread-safe.
Même si un module est thread-safe, cela ne signifie pas que le module est optimisé pour bien fonctionner
avec des fils. Un module pourrait éventuellement être réécrit pour utiliser les nouvelles fonctionnalités dans les threads
Perl pour augmenter les performances dans un environnement threadé.
Si vous utilisez un module qui n'est pas thread-safe pour une raison quelconque, vous pouvez vous protéger
en l'utilisant à partir d'un seul et unique thread. Si vous avez besoin de plusieurs threads pour accéder
un tel module, vous pouvez utiliser des sémaphores et beaucoup de discipline de programmation pour contrôler l'accès
à cela. Les sémaphores sont couverts dans "Sémaphores de base".
Voir aussi « Sécurité des threads des bibliothèques système ».
Fil à coudre Basics
Le module threads fournit les fonctions de base dont vous avez besoin pour écrire des programmes threadés. Dans
les sections suivantes, nous couvrirons les bases, vous montrant ce que vous devez faire pour créer
un programme fileté. Après cela, nous passerons en revue certaines des fonctionnalités du module de threads
qui facilitent la programmation threadée.
Basic Fil à coudre Assistance
La prise en charge des threads est une option de compilation de Perl. C'est quelque chose qui est activé ou désactivé lorsque
Perl est construit sur votre site, plutôt que lorsque vos programmes sont compilés. Si votre Perl
n'a pas été compilé avec la prise en charge des threads activée, toute tentative d'utilisation de threads échouera.
Vos programmes peuvent utiliser le module Config pour vérifier si les threads sont activés. Si votre
programme ne peut pas fonctionner sans eux, vous pouvez dire quelque chose comme :
utilisez la configuration ;
$Config{useithreads} ou
die('Recompilez Perl avec des threads pour exécuter ce programme.');
Un programme potentiellement threadé utilisant un module potentiellement threadé peut avoir un code comme celui-ci :
utilisez la configuration ;
utiliser MyMod ;
COMMENCER {
si ($Config{useithreads}) {
# Nous avons des fils
nécessite MyMod_threaded ;
importer MyMod_threaded ;
} Else {
nécessite MyMod_unthreaded ;
importer MyMod_unthreaded ;
}
}
Étant donné que le code qui s'exécute à la fois avec et sans threads est généralement assez désordonné, il est préférable de
isoler le code spécifique au thread dans son propre module. Dans notre exemple ci-dessus, c'est ce que
"MyMod_threaded" l'est, et il n'est importé que si nous fonctionnons sur un thread Perl.
A Note à propos le Exemples
Dans une situation réelle, il faut veiller à ce que tous les threads aient fini de s'exécuter avant
le programme se termine. Ce soin a pas été prise dans ces exemples dans l'intérêt de
simplicité. Exécuter ces exemples as is produira des messages d'erreur, généralement causés par
le fait qu'il y a encore des threads en cours d'exécution lorsque le programme se termine. Vous ne devriez pas être
alarmé par cela.
La création Threads
Le module de threads fournit les outils dont vous avez besoin pour créer de nouveaux threads. Comme n'importe quel autre
module, vous devez dire à Perl que vous souhaitez l'utiliser ; « utiliser des fils ; » importe tous les
pièces dont vous avez besoin pour créer des fils de base.
Le moyen le plus simple et le plus direct de créer un fil est avec "create()":
utiliser des fils ;
mon $thr = threads->create(\&sub1) ;
sous sous1 {
print("Dans le fil\n");
}
La méthode "create()" prend une référence à un sous-programme et crée un nouveau thread qui
commence à s'exécuter dans le sous-programme référencé. Le contrôle passe ensuite à la fois au sous-programme
et l'appelant.
Si vous en avez besoin, votre programme peut transmettre des paramètres à la sous-routine dans le cadre du thread
Commencez. Incluez simplement la liste des paramètres dans le cadre de l'appel "threads->create()",
comme ça:
utiliser des fils ;
mon $Param3 = 'foo';
my $thr1 = threads->create(\&sub1, 'Param 1', 'Param 2', $Param3) ;
ma @ParamList = (42, 'Bonjour', 3.14) ;
my $thr2 = threads->create(\&sub1, @ParamList);
my $thr3 = threads->create(\&sub1, qw(Param1 Param2 Param3));
sous sous1 {
mes @InboundParameters = @_;
print("Dans le fil\n");
print('Paramètres obtenus >', join('<>',@InboundParameters), "<\n");
}
Le dernier exemple illustre une autre caractéristique des threads. Vous pouvez générer plusieurs
threads utilisant le même sous-programme. Chaque thread exécute le même sous-programme, mais dans un
thread séparé avec un environnement séparé et des arguments potentiellement séparés.
"new()" est un synonyme de "create()".
Attendre Pour A Fil à coudre À Sortie
Étant donné que les threads sont également des sous-routines, ils peuvent renvoyer des valeurs. Attendre qu'un fil se termine
et extraire toutes les valeurs qu'il pourrait renvoyer, vous pouvez utiliser la méthode "join()":
utiliser des fils ;
my ($thr) = threads->create(\&sub1) ;
my @ReturnData = $thr->join();
print('Thread retourné ', join(', ', @ReturnData), "\n");
sub sub1 { return ('Cinquante-six', 'foo', 2); }
Dans l'exemple ci-dessus, la méthode "join()" revient dès que le thread se termine. en outre
à attendre qu'un thread se termine et à rassembler toutes les valeurs que le thread pourrait avoir
renvoyé, "join()" effectue également tout nettoyage du système d'exploitation nécessaire pour le thread. Ce nettoyage
peut être important, en particulier pour les programmes de longue durée qui génèrent de nombreux threads. Si
vous ne voulez pas les valeurs de retour et ne voulez pas attendre la fin du fil, vous
devrait appeler la méthode "detach()" à la place, comme décrit ci-dessous.
REMARQUE : Dans l'exemple ci-dessus, le thread renvoie une liste, ce qui nécessite que le thread
l'appel de création soit effectué dans un contexte de liste (c'est-à-dire, "mon ($thr)"). Voir "$thr->joindre()" dans les fils
et "THREAD CONTEXT" dans les threads pour plus de détails sur le contexte des threads et les valeurs de retour.
En ignorant A Fil à coudre
"join()" fait trois choses : il attend qu'un thread se termine, nettoie après et renvoie
toutes les données que le thread a pu produire. Mais que faire si vous n'êtes pas intéressé par le fil
renvoient des valeurs, et vous ne vous souciez pas vraiment de la fin du fil ? Tout ce que vous voulez, c'est pour le
fil à nettoyer une fois que c'est fait.
Dans ce cas, vous utilisez la méthode "detach()". Une fois qu'un thread est détaché, il s'exécutera jusqu'à ce que
c'est fini; alors Perl nettoiera après lui automatiquement.
utiliser des fils ;
mon $thr = threads->create(\&sub1) ; # Faire apparaître le fil
$thr->detach(); # Maintenant, nous ne nous en soucions officiellement plus
sleep(15) ; # Laisser le fil s'exécuter pendant un moment
sous sous1 {
mon compte $ = 0 ;
tandis que (1) {
$ count ++;
print("\$count est $count\n");
sleep(1);
}
}
Une fois qu'un thread est détaché, il peut ne pas être joint et toutes les données de retour qu'il pourrait avoir
produit (s'il a été fait et en attente d'une jointure) est perdu.
"detach()" peut également être appelé en tant que méthode de classe pour permettre à un thread de se détacher :
utiliser des fils ;
mon $thr = threads->create(\&sub1) ;
sous sous1 {
threads->détacher();
# Faire plus de travail
}
Processus et Fil à coudre Résiliation
Avec les threads, il faut veiller à ce qu'ils aient tous une chance de s'exécuter jusqu'à la fin,
en supposant que c'est ce que vous voulez.
Une action qui met fin à un processus se terminera tous fils en cours d'exécution. mourir() et sortie()
ont cette propriété, et perl fait une sortie lorsque le thread principal se termine, peut-être implicitement
en tombant à la fin de votre code, même si ce n'est pas ce que vous voulez.
À titre d'exemple de ce cas, ce code affiche le message « Perl a quitté les threads actifs :
2 en cours d'exécution et non joint :
utiliser des fils ;
mon $thr1 = threads->new(\&thrsub, "test1");
mon $thr2 = threads->new(\&thrsub, "test2");
sous-thrsub {
mon ($message) = @_;
dormir 1 ;
print "fil $message\n" ;
}
Mais quand les lignes suivantes sont ajoutées à la fin :
$thr1->join();
$thr2->join();
il imprime deux lignes de sortie, un résultat peut-être plus utile.
Threads Et Date
Maintenant que nous avons couvert les bases des threads, il est temps de passer à notre prochain sujet : les données.
Le threading introduit quelques complications à l'accès aux données que les programmes non-threadés
jamais besoin de s'inquiéter.
Owned Et Non partagé Date
La plus grande différence entre Perl fils et l'ancien filetage de style 5.005, ou pour
ce qui compte, pour la plupart des autres systèmes de threads, c'est que par défaut, aucune donnée n'est
partagé. Lorsqu'un nouveau thread Perl est créé, toutes les données associées au thread actuel
est copié dans le nouveau thread, et est par la suite privé à ce nouveau thread ! C'est
similaire en termes à ce qui se passe lorsqu'un processus Unix bifurque, sauf que dans ce cas, le
les données sont simplement copiées dans une partie différente de la mémoire au sein du même processus plutôt que dans un
véritable fourche en cours.
Pour utiliser le threading, cependant, on veut généralement que les threads partagent au moins une partie
données entre eux. Cela se fait avec le module threads::shared et le ":shared"
attribut:
utiliser des fils ;
utiliser threads::shared;
mon $foo :shared = 1;
ma barre $ = 1 ;
threads->create(sub { $foo++; $bar++; })->join();
print("$foo\n"); # Affiche 2 puisque $foo est partagé
print("$bar\n"); # Affiche 1 car $bar n'est pas partagé
Dans le cas d'un tableau partagé, tous les éléments du tableau sont partagés, et pour un hachage partagé,
toutes les clés et valeurs sont partagées. Cela impose des restrictions sur ce qui peut être attribué à
éléments de tableau et de hachage partagés : seules les valeurs simples ou les références à des variables partagées sont
autorisé - c'est pour qu'une variable privée ne puisse pas devenir accidentellement partagée. Un mauvais
l'affectation entraînera la mort du thread. Par exemple:
utiliser des fils ;
utiliser threads::shared;
mon $var = 1 ;
mon $svar :shared = 2;
mon %hash :partagé;
... créer des fils de discussion ...
$hash{a} = 1 ; # Tous les fils de discussion existent($hash{a})
# et $hash{a} == 1
$hash{a} = $var; # d'accord - copie par valeur : même effet que précédemment
$hash{a} = $svar; # d'accord - copie par valeur : même effet que précédemment
$hash{a} = \$svar; # okay - une référence à une variable partagée
$hash{a} = \$var; # Cela va mourir
delete($hash{a}); # ok - tous les threads verront !exists($hash{a})
Notez qu'une variable partagée garantit que si deux threads ou plus tentent de la modifier au
en même temps, l'état interne de la variable ne sera pas corrompu. Cependant, là
n'y a aucune garantie au-delà de cela, comme expliqué dans la section suivante.
Fil à coudre Pièges Courses
Bien que les threads apportent un nouvel ensemble d'outils utiles, ils présentent également un certain nombre d'écueils. Une
l'écueil est la condition de course :
utiliser des fils ;
utiliser threads::shared;
mon $x :shared = 1;
mon $thr1 = threads->create(\&sub1) ;
mon $thr2 = threads->create(\&sub2) ;
$thr1->join();
$thr2->join();
print("$x\n");
sub sub1 { mon $foo = $x; $x = $foo + 1 ; }
sub sub2 { ma $bar = $x; $x = $bar + 1 ; }
Que pensez-vous que $x sera ? La réponse est malheureusement it dépend. "sub1()" et
"sub2()" accède à la variable globale $x, une fois pour lire et une fois pour écrire. Cela dépend de
facteurs allant de l'algorithme de planification de votre implémentation de thread à la phase de la
lune, $x peut être 2 ou 3.
Les conditions de concurrence sont causées par un accès non synchronisé aux données partagées. Sans explicite
synchronisation, il n'y a aucun moyen d'être sûr que rien n'est arrivé aux données partagées
entre le moment où vous y accédez et le moment où vous le mettez à jour. Même ce simple fragment de code
a la possibilité d'erreur:
utiliser des fils ;
mon $x :shared = 2;
mon $y :partagé;
mon $z :partagé;
my $thr1 = threads->create(sub { $y = $x; $x = $y + 1; });
my $thr2 = threads->create(sub { $z = $x; $x = $z + 1; });
$thr1->join();
$thr2->join();
Deux threads accèdent tous les deux à $x. Chaque thread peut potentiellement être interrompu à tout moment, ou
être exécuté dans n'importe quel ordre. À la fin, $x pourrait être 3 ou 4, et $y et $z pourraient être 2
ou 3.
Même "$x += 5" ou "$x++" ne sont pas garantis comme étant atomiques.
Chaque fois que votre programme accède à des données ou à des ressources accessibles par d'autres threads,
vous devez prendre des mesures pour coordonner l'accès ou risquer l'incohérence des données et les conditions de concurrence.
Notez que Perl protégera ses composants internes de vos conditions de course, mais il ne protégera pas
vous de vous.
Synchronisation et des bactéries
Perl fournit un certain nombre de mécanismes pour coordonner les interactions entre eux et
leurs données, pour éviter les conditions de course et autres. Certains d'entre eux sont conçus pour ressembler
les techniques courantes utilisées dans les bibliothèques de threads telles que les "pthreads" ; d'autres sont Perl-
spécifique. Souvent, les techniques standard sont maladroites et difficiles à maîtriser (comme
l'état attend). Dans la mesure du possible, il est généralement plus facile d'utiliser des techniques Perlish telles que
files d'attente, ce qui supprime une partie du travail acharné impliqué.
Contrôle accès: fermer à clé()
La fonction "lock()" prend une variable partagée et y met un verrou. Aucun autre fil ne peut
verrouiller la variable jusqu'à ce qu'elle soit déverrouillée par le thread qui maintient le verrou. Déverrouillage
se produit automatiquement lorsque le thread de verrouillage quitte le bloc qui contient l'appel à
la fonction "lock()". L'utilisation de "lock()" est simple : cet exemple comporte plusieurs
threads effectuant des calculs en parallèle et mettant occasionnellement à jour un total cumulé :
utiliser des fils ;
utiliser threads::shared;
mon $total :shared = 0;
sous-calcul {
tandis que (1) {
mon $résultat ;
# (... faire quelques calculs et définir $result ...)
{
verrou ($total); # Bloquer jusqu'à obtenir le cadenas
$total += $résultat ;
} # Verrou libéré implicitement à la fin de la portée
dernier si $résultat == 0 ;
}
}
mon $thr1 = threads->create(\&calc);
mon $thr2 = threads->create(\&calc);
mon $thr3 = threads->create(\&calc);
$thr1->join();
$thr2->join();
$thr3->join();
print("total=$total\n");
"lock()" bloque le thread jusqu'à ce que la variable verrouillée soit disponible. Quand "lock()"
renvoie, votre thread peut être sûr qu'aucun autre thread ne peut verrouiller cette variable jusqu'à ce que le
bloc contenant les sorties d'écluse.
Il est important de noter que les verrous n'empêchent pas l'accès à la variable en question, seulement
tentatives de verrouillage. Ceci est conforme à la longue tradition de Perl en matière de courtoisie
programmation, et le verrouillage de fichier consultatif que "flock()" vous donne.
Vous pouvez verrouiller des tableaux et des hachages ainsi que des scalaires. Le verrouillage d'un tableau, cependant, ne
bloquer les verrous ultérieurs sur les éléments du tableau, il suffit de verrouiller les tentatives sur le tableau lui-même.
Les verrous sont récursifs, ce qui signifie qu'un thread peut verrouiller une variable plusieurs fois.
Le verrou durera jusqu'à ce que le "lock()" le plus à l'extérieur de la variable soit hors de portée. Pour
Exemple:
mon $x :partagé;
fais-le();
sous doit {
{
{
verrou ($x); # Attendez le verrouillage
verrou ($x); # NOOP - nous avons déjà la serrure
{
verrou ($x); # NOOP
{
verrou ($x); # NOOP
lockit_some_more();
}
}
} # *** Déverrouillage implicite ici ***
}
}
sous lockit_some_more {
verrou ($x); # NOOP
} # Rien ne se passe ici
Notez qu'il n'y a pas de fonction "unlock()" - la seule façon de déverrouiller une variable est d'autoriser
il pour sortir de la portée.
Un verrou peut être utilisé pour protéger les données contenues dans la variable verrouillée, ou
il peut être utilisé pour protéger autre chose, comme une section de code. Dans ce dernier cas, le
variable en question ne contient aucune donnée utile et n'existe que dans le but de
étant verrouillé. A cet égard, la variable se comporte comme les mutex et les sémaphores basiques
des bibliothèques de threads traditionnelles.
A Fil à coudre Piège: Les impasses
Les serrures sont un outil pratique pour synchroniser l'accès aux données, et les utiliser correctement est la clé
pour sécuriser les données partagées. Malheureusement, les serrures ne sont pas sans danger, surtout quand
plusieurs verrous sont impliqués. Considérez le code suivant :
utiliser des fils ;
mon $x :shared = 4;
my $y :shared = 'foo';
my $thr1 = threads->create(sub {
verrou ($x);
sleep(20);
verrou ($y);
});
my $thr2 = threads->create(sub {
verrou ($y);
sleep(20);
verrou ($x);
});
Ce programme se bloquera probablement jusqu'à ce que vous le tuiez. La seule façon pour qu'il ne se bloque pas, c'est si l'on
des deux threads acquiert les deux verrous en premier. Une version garantie à accrocher est plus
compliqué, mais le principe est le même.
Le premier thread va prendre un verrou sur $x, puis, après une pause pendant laquelle le second
thread a probablement eu le temps de travailler, essayez de verrouiller $y. Pendant ce temps, le
le deuxième thread attrape un verrou sur $y, puis essaie plus tard d'attraper un verrou sur $x. La deuxième serrure
tentative pour les deux threads se bloquera, chacun attendant que l'autre libère son verrou.
Cette condition s'appelle un blocage et se produit chaque fois que deux threads ou plus essaient
pour obtenir des verrous sur les ressources que les autres possèdent. Chaque thread se bloquera, en attendant le
autre pour libérer un verrou sur une ressource. Cela n'arrive jamais, cependant, puisque le fil avec
la ressource attend elle-même la libération d'un verrou.
Il existe plusieurs manières de traiter ce genre de problème. Le meilleur moyen est d'avoir toujours
tous les threads acquièrent des verrous exactement dans le même ordre. Si, par exemple, vous verrouillez des variables
$x, $y et $z verrouillent toujours $x avant $y et $y avant $z. Il est également préférable de s'accrocher
se bloque pendant une durée aussi courte afin de minimiser les risques de blocage.
Les autres primitives de synchronisation décrites ci-dessous peuvent souffrir de problèmes similaires.
Files d'attente : En passant Date Environ
Une file d'attente est un objet thread-safe spécial qui vous permet de mettre des données à une extrémité et de les retirer
l'autre sans avoir à se soucier des problèmes de synchronisation. Ils sont jolis
simple et ressemble à ceci :
utiliser des fils ;
utiliser Thread::Queue;
my $DataQueue = Thread::Queue->new();
my $thr = threads->create(sub {
while (mon $DataElement = $DataQueue->dequeue()) {
print("Supprimé $DataElement de la file d'attente\n");
}
});
$DataQueue->Enqueue(12);
$DataQueue->enqueue("A", "B", "C");
sleep(10);
$DataQueue->enqueue(undef);
$thr->join();
Vous créez la file d'attente avec "Thread::Queue->new()". Ensuite, vous pouvez ajouter des listes de scalaires sur
la fin avec « enqueue() » et les scalaires de suppression devant avec « dequeue() ». Une queue
n'a pas de taille fixe et peut grandir au besoin pour contenir tout ce qui y est poussé.
Si une file d'attente est vide, "dequeue()" bloque jusqu'à ce qu'un autre thread mette quelque chose en file d'attente. Cette
rend les files d'attente idéales pour les boucles d'événements et autres communications entre les threads.
Sémaphores : Synchronisation Date Accéder
Les sémaphores sont une sorte de mécanisme de verrouillage générique. Dans leur forme la plus basique, ils se comportent
très semblable aux scalaires verrouillables, sauf qu'ils ne peuvent pas contenir de données et qu'ils doivent être
explicitement déverrouillé. Dans leur forme avancée, ils agissent comme une sorte de compteur, et peuvent
permettre à plusieurs threads d'avoir le bloquer à n'importe quel moment.
Basic sémaphores
Les sémaphores ont deux méthodes, "down()" et "up()": "down()" décrémente le nombre de ressources,
tandis que "up()" l'incrémente. Les appels à "down()" bloqueront si le nombre actuel du sémaphore
décrémenterait en dessous de zéro. Ce programme donne une démonstration rapide :
utiliser des fils ;
utiliser Thread::Semaphore;
my $semaphore = Thread::Semaphore->new();
ma $GlobalVariable :shared = 0;
$thr1 = threads->create(\&sample_sub, 1);
$thr2 = threads->create(\&sample_sub, 2);
$thr3 = threads->create(\&sample_sub, 3);
sous-échantillon_sous {
mon $SubNumber = shift(@_);
mon $TryCount = 10 ;
mon $LocalCopy ;
sleep(1);
tandis que ($TryCount--) {
$sémaphore->down();
$LocalCopy = $GlobalVariable ;
print("$TryCount essaie de gauche pour le sous $SubNumber "
."(\$GlobalVariable est $GlobalVariable)\n");
sleep(2);
$CopieLocale++ ;
$GlobalVariable = $LocalCopy ;
$sémaphore->up();
}
}
$thr1->join();
$thr2->join();
$thr3->join();
Les trois invocations du sous-programme fonctionnent toutes en synchronisation. Le sémaphore, cependant, fait
assurez-vous qu'un seul thread accède à la variable globale à la fois.
Avancé Sémaphores
Par défaut, les sémaphores se comportent comme des verrous, ne laissant qu'un seul thread les "down()" à la fois.
Cependant, il existe d'autres utilisations des sémaphores.
Chaque sémaphore a un compteur qui lui est attaché. Par défaut, les sémaphores sont créés avec le
compteur mis à un, "down()" décrémente le compteur de un et "up()" incrémente de un.
Cependant, nous pouvons remplacer tout ou partie de ces valeurs par défaut simplement en passant différents
valeurs:
utiliser des fils ;
utiliser Thread::Semaphore;
my $semaphore = Thread::Semaphore->nouvelle(5);
# Crée un sémaphore avec le compteur mis à cinq
mon $thr1 = threads->create(\&sub1) ;
mon $thr2 = threads->create(\&sub1) ;
sous sous1 {
$sémaphore->down(5) ; # Décrémente le compteur de cinq
# Faites des trucs ici
$sémaphore->up(5) ; # Incrémenter le compteur de cinq
}
$thr1->detach();
$thr2->detach();
Si "down()" tente de décrémenter le compteur en dessous de zéro, il se bloque jusqu'à ce que le compteur soit
assez large. Notez que bien qu'un sémaphore puisse être créé avec un nombre de départ de zéro,
tout "up()" ou "down()" change toujours le compteur d'au moins un, et ainsi
"$sémaphore->down(0)" est le même que "$sémaphore->down(1) ".
La question, bien sûr, est pourquoi feriez-vous quelque chose comme ça? Pourquoi créer un sémaphore
avec un décompte de départ qui n'est pas un, ou pourquoi le décrémenter ou l'incrémenter de plus d'un ?
La réponse est la disponibilité des ressources. De nombreuses ressources dont vous souhaitez gérer l'accès
peut être utilisé en toute sécurité par plusieurs threads à la fois.
Par exemple, prenons un programme piloté par GUI. Il a un sémaphore qu'il utilise pour
synchroniser l'accès à l'affichage, de sorte qu'un seul thread dessine à la fois. Pratique, mais
bien sûr, vous ne voulez pas qu'un fil commence à dessiner tant que les choses ne sont pas correctement configurées. Dans
dans ce cas, vous pouvez créer un sémaphore avec un compteur mis à zéro, et l'augmenter quand les choses
sont prêts pour le dessin.
Les sémaphores avec des compteurs supérieurs à un sont également utiles pour établir des quotas. Dire,
par exemple, que vous avez un certain nombre de threads qui peuvent effectuer des E/S à la fois. tu ne veux pas
tous les threads lisant ou écrivant en même temps, car cela peut potentiellement submerger votre
canaux d'E/S, ou épuisez le quota de descripteurs de fichiers de votre processus. Vous pouvez utiliser un sémaphore
initialisé au nombre de demandes d'E/S simultanées (ou de fichiers ouverts) que vous souhaitez à tout moment
une fois, et que vos threads se bloquent et se débloquent tranquillement.
Des incréments ou des décréments plus importants sont pratiques dans les cas où un fil doit être extrait
ou retourner un certain nombre de ressources à la fois.
Attendre pour a État
Les fonctions "cond_wait()" et "cond_signal()" peuvent être utilisées avec des verrous pour
notifier aux threads coopérants qu'une ressource est devenue disponible. Ils sont très similaires en
utiliser aux fonctions trouvées dans "pthreads". Cependant, dans la plupart des cas, les files d'attente sont plus simples à
utilisation et plus intuitif. Voir threads::shared pour plus de détails.
Don up des bactéries
Il y a des moments où vous pouvez trouver utile qu'un thread abandonne explicitement le CPU à
un autre fil. Vous faites peut-être quelque chose de gourmand en processeur et voulez vous assurer que
le thread de l'interface utilisateur est appelé fréquemment. Quoi qu'il en soit, il y a des moments où vous
peut-être souhaiterez-vous qu'un thread abandonne le processeur.
Le package de threads de Perl fournit la fonction "yield()" qui fait cela. "rendement()" est
assez simple, et fonctionne comme ceci:
utiliser des fils ;
sous-boucle {
mon $thread = shift;
mon $foo = 50 ;
while($foo--) { print("Dans le fil $thread\n"); }
threads->yield();
$foo = 50 ;
while($foo--) { print("Dans le fil $thread\n"); }
}
my $thr1 = threads->create(\&loop, 'first');
my $thr2 = threads->create(\&loop, 'second');
my $thr3 = threads->create(\&loop, 'third');
Il est important de se rappeler que "yield()" n'est qu'un indice pour abandonner le CPU, cela dépend
sur votre matériel, votre système d'exploitation et vos bibliothèques de threads, ce qui se passe réellement. On de nombreuses d'exploitation
Systèmes, rendement() is a non-op. Il est donc important de noter qu'il ne faut pas construire
l'ordonnancement des threads autour des appels "yield()". Cela pourrait fonctionner sur votre plate-forme, mais
cela ne fonctionnera pas sur une autre plate-forme.
Général Fil à coudre Services Publics Routines
Nous avons couvert les parties les plus performantes du package d'enfilage de Perl, et avec ces outils vous
devrait être sur la bonne voie pour écrire du code threadé et des packages. Il y a quelques utiles
petits morceaux qui ne rentraient pas vraiment ailleurs.
Organisateur Ce que Fil à coudre Am I Dans?
La méthode de classe "threads->self()" fournit à votre programme un moyen d'obtenir un objet
représentant le thread dans lequel il se trouve actuellement. Vous pouvez utiliser cet objet de la même manière que le
ceux renvoyés par la création du fil.
Fil à coudre ID
"tid()" est une méthode d'objet de thread qui renvoie l'ID de thread du thread de l'objet
représente. Les ID de thread sont des entiers, le thread principal d'un programme étant 0.
Actuellement, Perl attribue un TID unique à chaque thread jamais créé dans votre programme,
attribuer au premier thread à créer un TID de 1, et augmenter le TID de 1 pour chaque
nouveau fil qui est créé. Lorsqu'il est utilisé comme méthode de classe, "threads->tid()" peut être utilisé par un
thread pour obtenir son propre TID.
Emplacements Ces Threads Le site de Même?
La méthode "equal()" prend deux objets thread et renvoie true si les objets représentent
le même fil, et faux s'ils ne le font pas.
Les objets de thread ont également une comparaison "==" surchargée afin que vous puissiez faire une comparaison sur
comme vous le feriez avec des objets normaux.
Organisateur Ce que Threads Emplacements En cours?
"threads->list()" renvoie une liste d'objets de thread, un pour chaque thread actuellement
en marche et non détaché. Pratique pour un certain nombre de choses, y compris le nettoyage à la fin
de votre programme (à partir du fil principal Perl, bien sûr) :
# Boucle à travers tous les threads
foreach my $thr (threads->list()) {
$thr->join();
}
Si certains threads n'ont pas fini de s'exécuter lorsque le thread Perl principal se termine, Perl vous avertira
vous à ce sujet et mourir, car il est impossible pour Perl de se nettoyer tandis que d'autres
les fils sont en cours d'exécution.
REMARQUE : le fil principal Perl (fil 0) est dans un détaché état, et n'apparaît donc pas dans
la liste renvoyée par "threads->list()".
A Complété Exemple
Vous êtes encore confus ? Il est temps qu'un exemple de programme montre certaines des choses que nous avons couvertes.
Ce programme trouve les nombres premiers à l'aide de threads.
1 # !/usr/bin/perl
2 # prime-pthread, avec l'aimable autorisation de Tom Christiansen
3
4 utilisation stricte ;
5 avertissements d'utilisation ;
6
7 utiliser des fils ;
8 utiliser Thread::Queue;
9
10 sous check_num {
11 my ($ en amont, $cur_prime) = @_;
12 mon $ enfant ;
13 my $downstream = Thread::Queue->new();
14 while (mon $num = $upstream->dequeue()) {
15 suivant sauf ($num % $cur_prime);
16 si ($enfant) {
17 $downstream->enqueue($num);
18 } autre {
19 print("Prime trouvé : $num\n");
20 $kid = threads->create(\&check_num, $downstream, $num);
21 si (! $enfant) {
22 warn("Désolé. Manque de threads.\n");
23 derniers;
24}
25}
26}
27 si ($enfant) {
28 $en aval->enqueue(undef);
29 $kid->join();
30}
31}
32
33 my $stream = Thread::Queue->new(3..1000, undef);
34 check_num ($ flux, 2);
Ce programme utilise le modèle de pipeline pour générer des nombres premiers. Chaque fil dans le
pipeline a une file d'attente d'entrée qui alimente les nombres à vérifier, un nombre premier qu'il est
responsable et une file d'attente de sortie dans laquelle il canalise les numéros qui ont échoué
Chèque. Si le thread a un numéro qui a échoué à sa vérification et qu'il n'y a pas de thread enfant,
alors le fil doit avoir trouvé un nouveau nombre premier. Dans ce cas, un nouveau thread enfant est
créé pour ce premier et collé à l'extrémité du pipeline.
Cela semble probablement un peu plus déroutant qu'il ne l'est en réalité, alors allons-y
programmez morceau par morceau et voyez ce qu'il fait. (Pour ceux d'entre vous qui pourraient essayer de
rappelez-vous exactement ce qu'est un nombre premier, c'est un nombre qui n'est divisible que par
lui-même et 1.)
Le gros du travail est effectué par la sous-routine "check_num()", qui prend une référence à
sa file d'attente d'entrée et un nombre premier dont il est responsable. Après avoir tiré dans l'entrée
file d'attente et le premier que le sous-programme vérifie (ligne 11), nous créons une nouvelle file d'attente (ligne
13) et réserver un scalaire pour le thread que nous allons probablement créer plus tard (ligne 12).
La boucle while de la ligne 14 à la ligne 26 récupère un scalaire dans la file d'attente d'entrée et vérifie
contre le premier ce fil est responsable. La ligne 15 vérifie s'il y a un
reste lorsque nous divisons le nombre à vérifier par notre premier. S'il y en a un, le
nombre ne doit pas être divisible par notre nombre premier, nous devons donc le transmettre au
fil suivant si nous en avons créé un (ligne 17) ou créez un nouveau fil si ce n'est pas le cas.
La nouvelle création de thread est la ligne 20. Nous lui transmettons une référence à la file d'attente que nous avons
créé, et le nombre premier que nous avons trouvé. Aux lignes 21 à 24, nous vérifions que
que notre nouveau fil a été créé, et sinon, nous arrêtons de vérifier tous les nombres restants dans le
file d'attente.
Enfin, une fois la boucle terminée (car nous avons un 0 ou "undef" dans la file d'attente, ce qui
sert de note pour terminer), nous transmettons la notification à notre enfant, et attendons qu'elle
exit si nous avons créé un enfant (lignes 27 et 30).
Pendant ce temps, de retour dans le thread principal, nous créons d'abord une file d'attente (ligne 33) et mettons en file d'attente tous les
numéros de 3 à 1000 pour vérification, plus un avis de résiliation. Ensuite, tout ce que nous avons à faire pour
lancer la balle est de passer la file d'attente et le premier premier à la sous-routine "check_num()"
(ligne 34).
C'est comme ça que ça marche. C'est assez simple; comme pour de nombreux programmes Perl, l'explication est
beaucoup plus longtemps que le programme.
Différents implémentations of discussions
Quelques informations sur les implémentations de threads du point de vue du système d'exploitation. Il y a
trois catégories de threads de base : les threads en mode utilisateur, les threads du noyau et le multiprocesseur
threads du noyau.
Les threads en mode utilisateur sont des threads qui vivent entièrement dans un programme et ses bibliothèques. Dans
ce modèle, le système d'exploitation ne connaît rien aux threads. En ce qui le concerne, votre processus est
juste un processus.
C'est le moyen le plus simple d'implémenter des threads et la façon dont la plupart des systèmes d'exploitation démarrent. Le grand
l'inconvénient est que, puisque le système d'exploitation ne connaît rien aux threads, si un thread se bloque, ils
le font tous. Les activités de blocage typiques incluent la plupart des appels système, la plupart des E/S et des choses comme
"dormir()".
Les threads du noyau sont la prochaine étape de l'évolution des threads. Le système d'exploitation connaît les threads du noyau,
et leur tient compte. La principale différence entre un thread de noyau et un utilisateur-
le fil de mode se bloque. Avec les threads du noyau, les choses qui bloquent un seul thread ne
bloquer les autres threads. Ce n'est pas le cas avec les threads en mode utilisateur, où le noyau bloque
au niveau du processus et non au niveau du thread.
Il s'agit d'un grand pas en avant, et peut donner à un programme threadé une amélioration considérable des performances par rapport à
programmes sans fil. Les threads qui bloquent les E/S, par exemple, ne seront pas bloqués
threads qui font d'autres choses. Chaque processus n'a toujours qu'un seul thread en cours d'exécution à
une fois, cependant, quel que soit le nombre de processeurs d'un système.
Étant donné que les threads du noyau peuvent interrompre un thread à tout moment, ils découvriront certains des
hypothèses de verrouillage implicite que vous pouvez faire dans votre programme. Par exemple, quelque chose comme
simple comme "$x = $x + 2" peut se comporter de manière imprévisible avec les threads du noyau si $x est visible pour
d'autres threads, car un autre thread peut avoir changé $x entre le moment où il a été récupéré
le côté droit et l'heure à laquelle la nouvelle valeur est stockée.
Les threads du noyau multiprocesseur sont la dernière étape de la prise en charge des threads. Avec multiprocesseur
threads du noyau sur une machine avec plusieurs processeurs, le système d'exploitation peut planifier deux threads ou plus pour
fonctionner simultanément sur différents processeurs.
Cela peut donner un sérieux coup de pouce aux performances de votre programme threadé, car plus d'un
thread s'exécutera en même temps. En contrepartie, cependant, n'importe lequel de ces
des problèmes de synchronisation qui n'auraient peut-être pas été affichés avec les threads de base du noyau apparaîtront
avec vengeance.
En plus des différents niveaux d'implication du système d'exploitation dans les threads, différents systèmes d'exploitation (et
différentes implémentations de threads pour un système d'exploitation particulier) allouent des cycles CPU aux threads dans
différentes façons.
Les systèmes multitâches coopératifs ont des threads en cours d'exécution qui abandonnent le contrôle si l'une des deux choses
arriver. Si un thread appelle une fonction de rendement, il abandonne le contrôle. Il abandonne aussi
contrôler si le thread fait quelque chose qui le bloquerait, comme effectuer des E/S.
Dans une implémentation multitâche coopérative, un thread peut priver tous les autres de CPU
temps s'il le souhaite.
Les systèmes multitâches préemptifs interrompent les threads à intervalles réguliers pendant que le système
décide quel thread doit s'exécuter ensuite. Dans un système multitâche préemptif, un thread
ne monopolisera généralement pas le processeur.
Sur certains systèmes, des threads coopératifs et préemptifs peuvent s'exécuter simultanément.
(Les threads exécutés avec des priorités en temps réel se comportent souvent de manière coopérative, par exemple,
les threads s'exécutant avec des priorités normales se comportent de manière préventive.)
De nos jours, la plupart des systèmes d'exploitation modernes prennent en charge le multitâche préemptif.
Performances considérations
La principale chose à garder à l'esprit lors de la comparaison de Perl fils à d'autres modèles de filetage est
le fait que pour chaque nouveau thread créé, une copie complète de toutes les variables et données
du thread parent doit être pris. Ainsi, la création de threads peut être assez coûteuse, à la fois
en termes d'utilisation de la mémoire et de temps passé à la création. Le moyen idéal pour réduire ces coûts
est d'avoir un nombre relativement court de threads de longue durée, tous créés assez tôt
(avant que le thread de base n'ait accumulé trop de données). Bien sûr, ce n'est pas toujours
possible, il faut donc faire des compromis. Cependant, une fois qu'un thread a été créé, son
les performances et l'utilisation de la mémoire supplémentaire devraient être légèrement différentes du code ordinaire.
Notez également que dans l'implémentation actuelle, les variables partagées utilisent un peu plus de mémoire
et sont un peu plus lents que les variables ordinaires.
Portée du processus Modifications
Notez que bien que les threads eux-mêmes soient des threads d'exécution séparés et que les données Perl soient des threads
privé à moins qu'il ne soit explicitement partagé, les threads peuvent affecter l'état de la portée du processus, affectant
tous les fils.
L'exemple le plus courant est la modification du répertoire de travail actuel à l'aide de "chdir()".
Un thread appelle "chdir()", et le répertoire de travail de tous les threads change.
Un exemple encore plus radical de changement de portée de processus est "chroot()" : le répertoire racine de
tous les threads changent, et aucun thread ne peut l'annuler (contrairement à "chdir()").
D'autres exemples de changements de portée de processus incluent "umask()" et la modification des uids et gids.
Vous songez à mélanger "fork()" et des threads ? Veuillez vous allonger et attendre jusqu'à ce que le sentiment
passe. Sachez que la sémantique de "fork()" varie d'une plate-forme à l'autre. Par exemple,
certains systèmes Unix copient tous les threads actuels dans le processus fils, tandis que d'autres ne
copiez le fil qui a appelé "fork()". Tu étais prévenu!
De même, le mélange des signaux et des threads peut être problématique. Les implémentations sont plate-forme-
dépendant, et même la sémantique POSIX peut ne pas être ce à quoi vous vous attendez (et Perl ne
vous donner l'API POSIX complète). Par exemple, il n'y a aucun moyen de garantir qu'un signal
envoyé à une application Perl multithread sera intercepté par un thread particulier.
(Cependant, une fonctionnalité récemment ajoutée offre la possibilité d'envoyer des signaux entre
fils. Voir "THREAD SIGNALLING" dans les threads pour plus de détails.)
Fil-Sécurité of Système Bibliothèques
Que les différents appels de bibliothèque soient thread-safe est hors du contrôle de Perl. Appelle souvent
souffrant de ne pas être thread-safe incluent: "localtime()", "gmtime()", fonctions
récupérer des informations sur l'utilisateur, le groupe et le réseau (telles que "getgrent()", "gethostent()",
"getnetent()" et ainsi de suite), "readdir()", "rand()" et "srand()". En général, les appels qui
dépendent d'un état externe global.
Si le système dans lequel Perl est compilé a des variantes thread-safe de tels appels, ils seront
utilisé. Au-delà de cela, Perl est à la merci de la sécurité des threads ou de l'insécurité des appels.
Veuillez consulter votre documentation d'appel de bibliothèque C.
Sur certaines plates-formes, les interfaces de bibliothèque thread-safe peuvent échouer si le tampon de résultat est trop
petite (par exemple, les bases de données des groupes d'utilisateurs peuvent être assez volumineuses et le réentrant
les interfaces peuvent avoir à transporter un instantané complet de ces bases de données). Perl va démarrer
avec un petit tampon, mais continuez à réessayer et à augmenter le tampon de résultat jusqu'à ce que le résultat
convient. Si cette croissance illimitée semble mauvaise pour des raisons de sécurité ou de consommation de mémoire, vous
peut recompiler Perl avec "PERL_REENTRANT_MAXSIZE" défini au nombre maximum d'octets
vous permettrez.
Conclusion
Un tutoriel de fil complet pourrait remplir un livre (et a, plusieurs fois), mais avec ce que nous avons
couvert dans cette introduction, vous devriez être sur la bonne voie pour devenir un Perl threadé
expert.
Utilisez perlthrtut en ligne à l'aide des services onworks.net
