Créer un pool de threads en C ++

Créer un pool de threads en C ++
Un pool de threads est un ensemble de threads où chaque fil a une sorte de tâche à effectuer. Donc, différents fils effectuent différents types de tâches. Ainsi, chaque fil a sa spécialisation des tâches. Une tâche est essentiellement une fonction. Des fonctions similaires sont effectuées par un thread particulier; Un ensemble de fonctions similaires similaires est effectué par un autre thread, et ainsi de suite. Bien qu'un thread exécutif exécute une fonction de niveau supérieur, un thread par définition est l'instanciation d'un objet de la classe de thread. Différents threads ont des arguments différents, donc un thread particulier doit s'occuper d'un ensemble similaire de fonctions.

En C ++, ce pool de fils doit être géré. C ++ n'a pas de bibliothèque pour créer un pool de threads et la gestion est. C'est probablement parce qu'il existe différentes façons de créer un pool de fils. Ainsi, un programmeur C ++ doit créer un pool de threads basé sur les besoins.

Qu'est-ce qu'un fil? Un thread est un objet instancié à partir de la classe de threads. En instanciation normale, le premier argument du constructeur de thread est le nom d'une fonction de niveau supérieur. Les autres arguments du constructeur de thread sont des arguments pour la fonction. Au fur et à mesure que le thread est instancié, la fonction commence à s'exécuter. La fonction c ++ main () est une fonction de niveau supérieur. D'autres fonctions en cette portée globale sont des fonctions de niveau supérieur. Il arrive que la fonction principale () est un thread qui n'a pas besoin de déclaration formelle comme le font les autres threads. Considérez le programme suivant:

#inclure
#inclure
Utilisation de Namespace Std;
void func ()
couter << "code for first output" << endl;
couter << "code for second output" << endl;

int main()

Thread Thr (func);
thr.rejoindre();
/ * Autres instructions * /
retour 0;

La sortie est:

Code pour la première sortie
code pour la deuxième sortie

Notez l'inclusion de la bibliothèque de threads qui a la classe de threads. func () est une fonction de haut niveau. La première instruction de la fonction principale () l'utilise dans l'instanciation du thread, thr. La déclaration suivante dans main (), est une déclaration de jointure. Il rejoint le fil thr au corps du thread de fonction principal (), à la position il est codé. Si cette instruction est absente, la fonction principale peut s'exécuter à la fin sans que la fonction de thread soit terminée. Cela signifie des ennuis.

Une commande similaire à ce qui suit doit être utilisée pour exécuter un programme C ++ 20 de threads, pour le compilateur G ++:

g ++ -std = c ++ 2a temp.cpp -lpthread -o temp

Cet article explique une façon de créer et de gérer un pool de fil en C++.

Contenu de l'article

  • Exigences d'exemple de pool de threads
  • Variables globales
  • La fonction de thread maître
  • fonction principale
  • Conclusion

Exigences d'exemple de pool de threads

Les exigences de ce pool de thread illustratif sont simples: il y a trois threads et un fil maître. Les fils sont subordonnés au fil maître. Chaque thread subordonné fonctionne avec une structure de données de file d'attente. Il y a donc trois files d'attente: Qu1, Qu2 et Qu3. La bibliothèque de file d'attente, ainsi que la bibliothèque de threads, doivent être incluses dans le programme.

Chaque file d'attente peut avoir plus d'un appel de fonction mais de la même fonction de niveau supérieur. C'est-à-dire que chaque élément d'une file d'attente est pour un appel de fonction d'une fonction de haut niveau particulière. Il existe donc trois fonctions de niveau supérieur différentes: une fonction de niveau supérieur par fil. Les noms de fonction sont FN1, FN2 et FN3.

La fonction appelle pour chaque file d'attente ne diffère que par leurs arguments. Pour la simplicité et pour cet exemple de programme, les appels de fonction n'auront aucun argument. En fait, la valeur de chaque file d'attente dans cet exemple sera le même entier: 1 que la valeur pour tous les éléments Qu1; 2 comme valeur pour tous les éléments Qu2; et 3 comme valeur pour tous les éléments Qu3.

Une file d'attente est une structure first_in-first_out. Ainsi, le premier appel (numéro) pour saisir une file d'attente est le premier à partir. Lorsqu'un appel (numéro) part, la fonction correspondante et son thread sont exécutés.

La fonction principale () est responsable de l'alimentation de chacune des trois files d'attente, avec des appels pour les fonctions appropriées, d'où des threads appropriés.

Le thread Master est responsable de la vérification s'il y a un appel dans une file d'attente, et s'il y a un appel, il appelle la fonction appropriée via son thread. Dans cet exemple de programme, quand aucune file d'attente n'a de thread, le programme se termine.

Les fonctions de niveau supérieur sont simples, pour cet exemple pédagogique, ils sont:

void fn1 ()
couter << "fn1" << endl;

void fn2 ()
couter << "fn2" << endl;

void fn3 ()
couter << "fn3" << endl;

Les fils correspondants seront thr1, thr2 et thr3. Le thread maître a sa propre fonction maître. Ici, chaque fonction n'a qu'une seule instruction. La sortie de la fonction fn1 () est «fn1». La sortie de la fonction fn2 () est «fn2». La sortie de la fonction fn3 () est «fn3».

À la fin de cet article, le lecteur peut assembler tous les segments de code de cet article pour former un programme de pool de threads.

Variables globales

Le sommet du programme avec les variables mondiales est:

#inclure
#inclure
#inclure
Utilisation de Namespace Std;
file d'attente qu1;
file d'attente qu2;
file d'attente qu3;
thread thr1;
thread thr2;
thread thr3;

Les variables de file d'attente et de thread sont des variables globales. Ils ont été déclarés sans initialisation ni déclaration. Après cela, dans le programme, devrait être les trois fonctions subordonnées de niveau supérieur, comme indiqué ci-dessus.

La bibliothèque iOStream est incluse pour l'objet cout. La bibliothèque de threads est incluse pour les threads. Les noms des fils sont thr1, thr2 et thr3. La bibliothèque de files d'attente est incluse pour les files d'attente. Les noms des files d'attente sont qu1, qu2 et qu3. qu1 correspond à thr1; qu2 correspond à thr2, et qu3 correspond à thr3. Une file d'attente est comme un vecteur, mais c'est pour FIFO (First_in-First_out).

La fonction de thread maître

Après les trois fonctions de niveau supérieur subordonnées sont la fonction maître du programme. C'est:

void MasterFn ()
travail:
if (qu1.size ()> 0) thr1 = thread (fn1);
si (qu2.size ()> 0) thr2 = thread (fn2);
if (qu3.size ()> 0) thr3 = thread (fn3);
if (qu1.size ()> 0)
qu1.populaire();
thr1.rejoindre();

si (qu2.size ()> 0)
qu2.populaire();
thr2.rejoindre();

if (qu3.size ()> 0)
qu3.populaire();
thr3.rejoindre();

if (qu1.size () == 0 && qu1.size () == 0 && qu1.size () == 0)
retour;
aller au travail;

La boucle Goto-Incarne tout le code de la fonction. Lorsque toutes les files d'attente sont vides, la fonction renvoie vide, avec l'instruction «return»;.

Le premier segment de code de la boucle de Goto-Tree a trois instructions: une pour chaque file d'attente et le fil correspondant. Ici, si une file d'attente n'est pas vide, son thread (et la fonction subordonnée de niveau supérieur correspondant) est exécutée.

Le segment de code suivant se compose de trois si-constructions, chacun correspondant à un thread subordonné. Chaque IF-Construct a deux déclarations. La première déclaration supprime le numéro (pour l'appel), qui aurait pu avoir lieu dans le premier segment de code. Le suivant est une déclaration de jointure, qui s'assure que le thread correspondant fonctionne à l'achèvement.

La dernière instruction de la boucle Goto-Fin termine la fonction, sortant de la boucle si toutes les files d'attente sont vides.

Fonction principale

Après la fonction de thread maître dans le programme, devrait être la fonction principale (), dont le contenu est:

qu1.push (1);
qu1.push (1);
qu1.push (1);
qu2.push (2);
qu2.push (2);
qu3.push (3);
Thread Masterthrh (MasterFn);
couter << "Program has started:" << endl;
maître.rejoindre();
couter << "Program has ended." << endl;

La fonction principale () est chargée de mettre des nombres qui représentent les appels dans les files d'attente. Qu1 a trois valeurs de 1; qu2 a deux valeurs de 2 et qu3 a une valeur de 3. La fonction principale () démarre le thread maître et le rejoint à son corps. Une sortie de l'ordinateur de l'auteur est:

Le programme a commencé:
fn2
fn3
FN1
FN1
fn2
FN1
Le programme a pris fin.

La sortie montre les opérations simultanées irrégulières des threads. Avant que la fonction Main () ne rejoigne son thread maître, il affiche "Le programme a commencé:". Le thread maître appelle Thr1 pour fn1 (), thr2 pour fn2 () et thr3 pour fn3 (), dans cet ordre. Cependant, la sortie correspondante commence par «FN2», puis «FN3», puis «FN1». Il n'y a rien de mal à cette commande initiale. C'est ainsi que fonctionne la concurrence, irrégulièrement. Les autres chaînes de sortie apparaissent comme leurs fonctions ont été appelées.

Une fois que le corps de fonction principale a rejoint le thread maître, il a attendu que le fil de maître se termine. Pour que le fil maître se termine, toutes les files d'attente doivent être vides. Chaque valeur de file d'attente correspond à l'exécution de son thread correspondant. Ainsi, pour que chaque file d'attente devienne vide, son fil doit s'exécuter pour ce nombre de fois; Il y a des éléments dans la file d'attente.

Lorsque le thread maître et ses threads ont été exécutés et terminés, la fonction principale continue de s'exécuter. Et il affiche: «Le programme a pris fin.".

Conclusion

Une piscine de threads est un ensemble de threads. Chaque fil est responsable de l'exécution de ses propres tâches. Les tâches sont des fonctions. En théorie, les tâches arrivent toujours. Ils ne se terminent pas vraiment, comme illustré dans l'exemple ci-dessus. Dans certains exemples pratiques, les données sont partagées entre les threads. Pour partager des données, le programmeur a besoin de connaissances sur condition_variable, fonction asynchrone, promesse et avenir. C'est une discussion pour une autre fois.