5 votes

Y a-t-il des limites au nombre de verrous qu'un programme Python peut créer ?

J'ai un programme python3 à un seul thread que j'essaie de convertir pour utiliser plusieurs threads. J'ai une structure de données en forme d'arbre qui est lue et écrite. Potentiellement, plusieurs threads voudront lire et écrire en même temps.

Une façon évidente d'y parvenir est d'avoir un seul verrou pour l'ensemble de la structure de données : personne ne peut lire pendant qu'une écriture est en cours, il ne peut y avoir plus d'une écriture à la fois, et aucune écriture ne peut avoir lieu lorsqu'il y a des lectures en attente.

Cependant, j'aimerais que le verrouillage soit plus fin pour améliorer les performances. Il s'agit d'un arbre complet à 16 branches qui, lorsqu'il est entièrement peuplé, compte environ 5 à 6 millions de feuilles (ce qui est généralement bien équilibré dans la pratique, mais rien n'est garanti). Si je voulais le verrouillage le plus fin, je pourrais verrouiller les parents des feuilles. Cela représenterait plus de 100 000 verrous.

Je dois admettre que je ne l'ai pas encore essayé. Mais j'ai pensé poser la question suivante : y a-t-il des limitations matérielles ou des raisons de performance qui devraient m'empêcher de créer autant d'objets de verrouillage ? En d'autres termes, devrais-je envisager de verrouiller jusqu'à, disons, la profondeur 2 à partir de la racine (256 verrous) ?

Merci pour vos conseils.

EDIT :

Plus d'informations :

Je ne connais pas encore le nombre de cœurs, car nous sommes encore en train d'expérimenter la puissance de calcul dont nous aurons besoin, mais je me risquerais à dire qu'une poignée de cœurs seulement sera utilisée.

Je vise environ 50 000 fils. Il y a des E/S asynchrones et un thread par socket. Pendant la phase d'amorçage du code, autant de threads que possible seront exécutés simultanément (dans la limite du matériel), mais c'est un coût unique. Ce qui nous intéresse le plus, c'est ce qui se passe une fois que les choses sont en place et fonctionnent. À ce stade, je dirais que seuls quelques milliers de threads par seconde sont en cours d'exécution. Je dois mesurer le temps de réponse, mais je pense qu'il est d'environ 10 ms par période de veille. Cela représente quelques dizaines de threads actifs à la fois (en moyenne).

Maintenant que je l'ai écrit, c'est peut-être la réponse à ma question. Si je n'ai besoin que de quelques dizaines de threads qui lisent ou écrivent à la fois, alors je n'ai pas vraiment besoin d'un verrouillage aussi fin sur l'arbre.

2voto

Brendan Abel Points 3745

Optimisation prématurée

Il s'agit d'un exemple classique d'optimisation prématurée. Sans savoir combien de temps vos threads passent à se bloquer, probablement en attendant que d'autres écritures se produisent, il n'est pas évident de savoir ce que vous avez à gagner en créant la complexité supplémentaire de la gestion de milliers de verrous.

Le verrou de l'interprète global

Le threading lui-même peut être une optimisation prématurée. Votre tâche est-elle facilement threadable ? Plusieurs threads peuvent-ils travailler en parallèle en toute sécurité ? Les tâches qui nécessitent une grande quantité d'états partagés (c'est-à-dire des verrous nombreux et fréquents) sont généralement de mauvais candidats pour un nombre élevé de threads. En Python, vous risquez d'en tirer encore moins d'avantages à cause de la GIL. Vos threads font-ils beaucoup d'entrées-sorties, appellent-ils des applications externes ou utilisent-ils des modules python écrits en C qui libèrent correctement la GIL ? Si ce n'est pas le cas, il se peut que le threading ne vous apporte aucun avantage. Vous pouvez contourner la GIL en utilisant l'option multiprocessing mais le passage des verrous et des écritures à travers les limites du processus entraîne des frais généraux et, ironiquement, il peut rendre votre application beaucoup plus lente

Files d'attente

Une autre option consiste à utiliser une file d'attente d'écriture. Si les threads n'ont pas besoin de partager l'état, mais qu'ils ont tous besoin d'écrire sur le même objet (c'est-à-dire qu'ils ne lisent que très peu cet objet), vous pouvez simplement ajouter les écritures à une file d'attente et faire en sorte qu'un seul thread traite les écritures, sans qu'aucun verrou ne soit nécessaire.

1voto

Armadillo Jim Points 183

En écho à Brendan Abel : éviter l'optimisation prématurée. Si j'ai 16 cœurs, il ne peut y avoir plus de 16 accès parallèles (lecture/écriture). Il est inutile d'avoir un million de verrous qui traînent. Un nombre limité de verrous suffit. La question qui se pose alors est la suivante : si j'ai un accès aléatoire à un ensemble de ressources, quelle est la probabilité que deux cœurs essaient d'accéder à la même ressource en même temps ?

Par exemple, si j'ai 16 cœurs qui essaient d'accéder à 16 ressources au hasard, le risque que l'un d'entre eux bloque l'autre est élevé. En d'autres termes, il y a très peu de permutations de 16 objets par rapport à 16 choix indépendants à partir de 16 objets : 16 ! contre 16^16. Le risque de no est d'environ 1 sur 1 million. (En revanche, si je dispose de 16 cœurs accédant à 256 ressources de manière aléatoire, le risque de blocage tombe à environ 38 %. Lorsque vous arrivez à 16 cœurs et 4096 ressources, le risque de blocage est inférieur à 3 %.

Ceci est lié à la paradoxe de l'anniversaire .

Remarque : je suppose ici qu'il n'est pas souhaitable qu'un thread en bloque un autre, que l'attente est inacceptable. Mais, comme ci-dessus, la chose à faire est de mesurer. N'investissez pas plus d'efforts techniques dans l'optimisation de quelque chose qui n'est tout simplement pas nécessaire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X