Je viens de passer en revue un code vraiment terrible - du code qui envoie des messages sur un port série en créant un nouveau thread à empaqueter 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 ne sais pas pourquoi quelqu'un ferait une telle chose, mais cela soulève la question - combien de frais généraux y a-t-il lors de la création d'un fil?
... envoie des messages sur un port série ... pour chaque message un pthread est créé, les bits sont correctement configurés, puis le thread se termine. ... combien de frais généraux y a-t-il lors de la création d'un fil?
Ceci est hautement spécifique au système. Par exemple, la dernière fois que j'ai utilisé le filetage VMS était cauchemardesque (des années, mais de mémoire, un fil pouvait créer quelque chose comme 10 de plus par seconde (et si vous gardiez cela pendant quelques secondes sans que les fils ne quittent, vous seriez au cœur)), alors que sous Linux, vous pouvez probablement en créer des milliers. Si vous voulez savoir exactement, comparez-le sur votre système. Mais, cela ne sert pas à grand-chose de simplement savoir cela sans en savoir plus sur les messages: s'ils font en moyenne 5 octets ou 100 Ko, s'ils sont envoyés de manière contiguë ou si la ligne est inactive entre les deux, et quelles sont les exigences de latence pour l'application sont toutes aussi pertinentes à la pertinence de l'utilisation du thread du code comme toute mesure absolue de la surcharge de création de thread. Et il n'est peut-être pas nécessaire que les performances soient le critère de conception dominant.
Pour ressusciter cet ancien thread, je viens de faire un code de test simple:
#include <thread>
int main(int argc, char** argv)
{
for (volatile int i = 0; i < 500000; i++)
std::thread([](){}).detach();
return 0;
}
Je l'ai compilé avec g++ test.cpp -std=c++11 -lpthread -O3 -o test
. Je l'ai ensuite exécuté trois fois de suite sur un ancien ordinateur portable lent (noyau 2.6.18) lourdement chargé (faisant une reconstruction de la base de données) (Intel Core i5-2540M). Résultats de trois séries consécutives: 5,647 s, 5,515 s et 5,561 s. Nous examinons donc un peu plus de 10 microsecondes par thread sur cette machine, probablement beaucoup moins sur la vôtre.
Ce n'est pas du tout une surcharge, étant donné que les ports série atteignent un maximum d'environ 1 bit par 10 microsecondes. Maintenant, bien sûr, il peut y avoir diverses pertes de threads supplémentaires impliquant des arguments passés/capturés (bien que les appels de fonction eux-mêmes puissent en imposer), des ralentissements du cache entre les cœurs (si plusieurs threads sur différents cœurs se battent sur la même mémoire en même temps), etc. Mais en général, je doute fortement que le cas d'utilisation que vous avez présenté ait un impact négatif sur les performances (et pourrait fournir des avantages, selon), bien que vous ayez déjà étiqueté de manière préventive le concept de "code vraiment terrible" sans même savoir combien de temps il faut pour lancer un fil.
Que ce soit une bonne idée ou non dépend beaucoup des détails de votre situation. De quoi d'autre le thread appelant est-il responsable? En quoi consiste précisément la préparation et la rédaction des paquets? À quelle fréquence sont-ils écrits (avec quel type de distribution? Uniformes, groupés, etc ...?) Et quelle est leur structure? Combien de cœurs le système possède-t-il? Etc. Selon les détails, la solution optimale pourrait aller de "pas de threads du tout" à "pool de threads partagé" à "thread pour chaque paquet".
Notez que les pools de threads ne sont pas magiques et peuvent dans certains cas être un ralentissement par rapport aux threads uniques, car l'un des plus gros ralentissements avec les threads est la synchronisation de la mémoire cache utilisée par plusieurs threads en même temps, et les pools de threads par leur nature même d'avoir pour rechercher et traiter les mises à jour à partir d'un thread différent, vous devez le faire. Ainsi, votre thread principal ou votre thread de traitement enfant peut rester bloqué si le processeur n'est pas sûr si l'autre processus a modifié une section de la mémoire. En revanche, dans une situation idéale, un thread de traitement unique pour une tâche donnée n'a à partager la mémoire avec sa tâche d'appel qu'une seule fois (lorsqu'elle est lancée) et ensuite ils ne s'interfèrent plus jamais.
On m'a toujours dit que la création de threads est bon marché, surtout par rapport à l'alternative de création d'un processus. Si le programme dont vous parlez n'a pas beaucoup d'opérations qui doivent être exécutées simultanément, le threading peut ne pas être nécessaire, et à en juger par ce que vous avez écrit, cela pourrait bien être le cas. Un peu de littérature pour me soutenir:
http://www.personal.kent.edu/~rmuhamma/OpSystems/Myos/threads.htm
Les fils sont bon marché dans le sens où
Ils n'ont besoin que d'une pile et d'un stockage pour les registres donc, les threads sont bon marché à créer.
Les threads utilisent très peu de ressources d'un système d'exploitation dans lequel ils fonctionnent. Autrement dit, les threads n'ont pas besoin de nouvel espace d'adressage, de données globales, de code de programme ou de ressources de système d'exploitation.
Les changements de contexte sont rapides lorsque vous travaillez avec des threads. La raison en est que nous n'avons qu'à enregistrer et/ou restaurer PC, SP et registres.
Plus du même ici .
Dans Operating System Concepts 8th Edition (page 155), les auteurs écrivent sur les avantages du threading:
L'allocation de mémoire et de ressources pour la création de processus est coûteuse. Parce que les threads partagent la ressource du processus auquel ils appartiennent, il est plus économique de créer et de contextualiser- commutez les fils. Il peut être difficile d'évaluer empiriquement la différence de surcharge, mais en général, la création et la gestion des processus prennent beaucoup plus de temps que les threads. Dans Solaris, par exemple, la création d'un processus est environ trente fois plus lente que la création d'un thread, et le changement de contexte est environ cinq fois plus lent.
Vous ne voulez certainement pas faire cela. Créez un seul thread ou un pool de threads et signalez simplement quand les messages sont disponibles. Lors de la réception du signal, le thread peut effectuer tout traitement de message nécessaire.
En termes de frais généraux, la création/destruction de threads, en particulier sous Windows, est assez coûteuse. Quelque part de l'ordre de dizaines de microsecondes, pour être précis. Elle ne devrait, pour la plupart, être effectuée qu'au début/à la fin d'une application, à l'exception peut-être des pools de threads redimensionnés dynamiquement.
Il y a des frais généraux dans la création de threads, mais en les comparant avec des vitesses de transmission généralement lentes du port série (19200 bits/sec étant le plus courant), cela n'a pas d'importance.
À titre de comparaison, jetez un œil à OSX: Lien
Structures de données du noyau: environ 1 Ko d'espace de pile: 512 Ko (threads secondaires): 8 Mo (thread principal OS X), 1 Mo (thread principal iOS)
Temps de création: environ 90 microsecondes
La création de threads posix devrait également être autour de cela (pas un chiffre éloigné), je suppose.
La création de thread et l'informatique dans un thread coûtent assez cher. Toutes les structures de données doivent être configurées, le thread enregistré auprès du noyau et un changement de thread doivent se produire pour que le nouveau thread soit réellement 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 l'article (mentionné en tapant) souligne que la création d'un thread n'est bon marché que par rapport à la création d'un processus. Dans l'ensemble, c'est assez cher.
Je n'utiliserais jamais de fil
Dans votre exemple, il serait logique (comme cela a déjà été souligné) de créer un thread qui gère toutes les communications série et est éternel.
hth
Mario
J'ai utilisé le design "terrible" ci-dessus dans une application VOIP que j'ai faite. Cela a très bien fonctionné ... absolument pas de latence ou de paquets manqués/perdus pour les ordinateurs connectés localement. Chaque fois qu'un paquet de données arrivait, un thread était créé et remis ces données pour le traiter aux périphériques de sortie. Bien sûr, les paquets étaient gros, donc cela n'a causé aucun goulot d'étranglement. Pendant ce temps, le thread principal pourrait reboucler pour attendre et recevoir un autre paquet entrant.
J'ai essayé d'autres conceptions où les fils dont j'ai besoin sont créés à l'avance mais cela crée ses propres problèmes. Vous devez d'abord concevoir correctement votre code pour que les threads récupèrent les paquets entrants et les traitent de manière déterministe. Si vous utilisez plusieurs threads (pré-alloués), il est possible que les paquets soient traités "hors service". Si vous utilisez un seul thread (pré-alloué) pour boucler et récupérer les paquets entrants, il est possible que le thread rencontre un problème et se termine sans laisser de threads pour traiter les données.
La création d'un thread pour traiter chaque paquet de données entrant fonctionne très proprement, en particulier sur les systèmes multicœurs et où les paquets entrants sont volumineux. Pour répondre plus directement à votre question, l'alternative à la création de threads consiste à 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 de détecter les 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 besoins.
Dans toute implémentation sensée, le coût de la création d'unité d'exécution doit ê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
et read
. Certaines mesures occasionnelles sur mon système ont montré que pthread_create
Prenait environ deux fois plus de temps que open("/dev/null", O_RDWR)
, ce qui est très cher par rapport au calcul pur mais très bon marché par rapport à tout IO ou d'autres opérations qui impliqueraient de basculer entre l'espace utilisateur et l'espace noyau.