Je viens de revoir un code vraiment terrible - un code qui envoie des messages sur un port série en créant un nouveau thread pour emballer et assembler le message dans un nouveau thread pour chaque message envoyé. Oui, pour chaque message, un pthread est créé, les bits sont correctement configurés, puis le thread se termine. Je n'ai aucune idée de la raison pour laquelle quelqu'un ferait une telle chose, mais cela soulève la question suivante : quelle est la surcharge lors de la création d'un thread ?
Réponses
Trop de publicités?J'ai utilisé le design "terrible" ci-dessus dans une application VOIP que j'ai réalisée. Cela a très bien fonctionné ... absolument aucune latence ou paquets manqués/abandonnés pour les ordinateurs connectés localement. Chaque fois qu'un paquet de données arrivait, un thread était créé et transmettait ces données pour les traiter vers les périphériques de sortie. Bien entendu, les paquets étaient volumineux, ce qui ne provoquait aucun goulot d'étranglement. Pendant ce temps, le thread principal pouvait revenir en boucle pour attendre et recevoir un autre paquet entrant.
J'ai essayé d'autres modèles où les fils dont j'ai besoin sont créés à l'avance, mais cela crée ses propres problèmes. Tout d'abord, vous devez concevoir votre code correctement pour que les threads puissent récupérer les paquets entrants et les traiter de manière déterministe. Si vous utilisez plusieurs threads (pré-alloués), il est possible que les paquets soient traités "dans le désordre". Si vous utilisez un seul thread (pré-alloué) pour boucler et récupérer les paquets entrants, il est possible que ce thread rencontre un problème et se termine, ne laissant aucun thread pour traiter les données.
La création d'un thread pour traiter chaque paquet de données entrant fonctionne très proprement, notamment sur les systèmes multicœurs et lorsque les paquets entrants sont volumineux. Pour répondre plus directement à votre question, l'alternative à la création de threads est de créer un processus d'exécution qui gère les threads pré-alloués. La possibilité de synchroniser le transfert et le traitement des données ainsi que la détection des erreurs peut ajouter autant, sinon plus, de frais généraux que la simple création d'un nouveau thread. Tout dépend de votre conception et de vos exigences.
La création de threads et le calcul dans un thread sont assez coûteux. Toutes les structures de données doivent être mises en place, le thread doit être enregistré auprès du noyau et un changement de thread doit se produire pour que le nouveau thread soit effectivement exécuté (dans un temps non spécifié et imprévisible). L'exécution de thread.start ne signifie pas que la fonction principale du thread est appelée immédiatement. Comme le souligne l'article (mentionné par typoking), la création d'un thread n'est bon marché que par rapport à la création d'un processus. Globalement, elle est assez coûteuse.
Je n'utiliserais jamais un fil
- pour un court calcul
- un calcul où j'ai besoin du résultat dans mon flux de code (que c'est-à-dire que je démarre le fil et j'attends qu'il renvoie le résultat de son calcul
Dans votre exemple, il serait logique (comme cela a déjà été souligné) de créer un thread qui gère toute la communication série et qui est éternel.
hth
Mario
À titre de comparaison, jetez un coup d'œil à OSX : Lien
-
Structures de données du noyau : Environ 1 Ko Espace pile : 512 Ko (threads secondaires) : 8 MB (thread principal OS X) , 1 MB (thread principal iOS) principal d'iOS)
-
Temps de création : Environ 90 microsecondes
La création du fil posix devrait également se situer autour de ce chiffre (pas très éloigné) je suppose.
Dans toute implémentation sensée, le coût de la création d'un thread devrait être proportionnel au nombre d'appels système qu'il implique, et du même ordre de grandeur que les appels système familiers comme open
y read
. Quelques mesures occasionnelles sur mon système ont montré pthread_create
prenant environ deux fois plus de temps que open("/dev/null", O_RDWR)
Ce qui est très coûteux par rapport au calcul pur, mais très bon marché par rapport à toute opération d'entrée/sortie ou autre qui impliquerait de passer de l'espace utilisateur à l'espace noyau.
C'est en effet très dépendant du système, j'ai testé le code de @Nafnlaus :
#include <thread>
int main(int argc, char** argv)
{
for (volatile int i = 0; i < 500000; i++)
std::thread([](){}).detach();
return 0;
}
Sur mon ordinateur de bureau Ryzen 5 2600 :
Windows 10, compilé avec la version MSVC 2019 en ajoutant des appels std::chrono autour de lui pour le chronométrer. Au repos (uniquement Firefox avec 217 onglets) :
Cela a pris environ 20 secondes (20.274, 19.910, 20.608) (également ~20 secondes avec Firefox fermé)
Ubuntu 18.04 compilé avec :
g++ main.cpp -std=c++11 -lpthread -O3 -o thread
chronométré avec :
time ./thread
Il a fallu environ 5 secondes (5.595, 5.230, 5.297)
Le même code sur mon raspberry pi 3B compilé avec :
g++ main.cpp -std=c++11 -lpthread -O3 -o thread
chronométré avec :
time ./thread
a pris environ 15 secondes (16.225, 14.689, 16.235)
0 votes
Un seul fil de travail permettra de résoudre certains des autres types de conflits de ressources que vous pourriez avoir avec plusieurs fils (écritures entrelacées, etc.).
0 votes
Je voudrais dire que oui, c'est une chose horrible à faire - ce qui m'amène à ma question, combien de frais généraux sont encourus lors de la création d'un fil (en général) Franchement, je ne sais pas comment déterminer ou même mesurer une implémentation de la bibliothèque pthread.
13 votes
Les frais généraux sont 0.37% si le message est de la bonne taille.