[Python] Multiplexage avec le module select

Avantage sur les threads

a marqué ce sujet comme résolu.

C’est juste une technique différente. Ça permet d’avoir des appels qui ne bloquent pas le thread courant et ça peut être utile quand on ne veut justement aps instacier de nouveaux threads et avoir à les gérer.

Par ailleurs, note que le module selectors fournit une interface plus haut-niveau de multiplexing. La page de documentation comporte aussi des exemples.

Salut,

À l’origine, il y a longtemps, les microprocesseurs disposaient d’un seul cœur, donc d’un seul « fil d’exécution » : un seul thread au niveau matériel. Les instructions étaient exécutées les unes après les autres :

| Instruction 1 | Instruction 2 | Instruction 3 | ... |
======>  Temps  ======>

Par la suite, et pour monter en performances plus facilement qu’en miniaturisant et rendant toujours plus puissants les microprocesseurs, on a décidé de mettre plusieurs microprocesseurs logiques les uns à côté des autres : c’est l’avènement du « multi-cœur ».

      /\
Cœurs ||  | Instruction 1 | Instruction 2 | Instruction 3 | ... |
      ||  | Instruction 4 | Instruction 5 | Instruction 6 | ... |
            ======>  Temps  ======>

Pour utiliser un chaque cœur d’un processeur multi-cœur, il faut soit lancer plusieurs programmes au niveau du système d’exploitation, soit lancer plusieurs threads au niveau d’un même programme.

Maintenant, imaginons que je veuille que mon programme écoute sur 10 sockets différentes. Logiquement, écouter sur un socket va bloquer mon programme, ou du moins 10 threads.

Si j’exécute plusieurs programmes et que mon système d’exploitation est multi-tâches, alors il va mutualiser le fil d’exécution du CPU entre différents programmes (si j’ai plus de programmes en exécution que de cœurs) :

| Instruction 1 programme A | Instruction 1 programme B | Instruction 2 programme A | ... |
======>  Temps  ======>

Il en est de même si j’ai différents threads (et que j’ai plus de threads en activité que de cœurs) :

| Instruction 1 thread A | Instruction 1 thread B | Instruction 2 thread B | ... |
======>  Temps  ======>

Maintenant, utiliser des threads a des inconvénients :

  • Tu peux faire communiquer des threads entre eux, mais c’est compliqué. Ils risquent de se marcher sur les pieds : que se passe-t-il si l’un s’apprête à modifier deux variables liées entre elles à la suite, commence à en modifier une, mais que le second thread lit les deux variables sans que la deuxième variable n’ait encore été modifiée, et qu’il obtient des résultats contradictoires en conséquence ? La programmation avec les threads est plein de bugs tordus comme ça.

    Pour les éviter, il faut « bloquer » explicitement des zones du code qui touchent une même variable pour éviter qu’elles ne soient exécutées par deux programmes à la fois, c’est compliqué et ça créé des sacrés sacs de nœuds.

  • Les threads représentent un léger coût en performance et en mémoire. Il faut que le système les réveillent, les endorment, les réveillent… avec toutes les variables et le contexte qui vont avec à chaque fois. C’est pour ça que les serveurs qui sont utilisés pour de fortes nécessités de performances, comme Nginx, évitent d’utiliser des threads.

Maintenant, quelle est l’alternative aux threads ? Les boucles événementielles. Le principe est simple : le programme se met d’accord avec le noyau pour écouter sur plusieurs sockets (ou plusieurs sockets + une entrée de terminal + un périphérique spécial, etc.) à la fois, et se faire réveiller par le noyau uniquement quand il y a de nouvelles données / un nouvel évènement à traiter.

Ainsi, il n’a qu’un seul fil d’exécution et qu’un seul état (et n’utilise qu’un seul cœur, sauf si tu commences à mettre une boucle événementielle sur chaque thread…).

Le module select permet d’utiliser une des API bas-niveau qui sont exposées par le noyau pour permettre des boucles événementielles sur plusieurs descripteurs (sockets, fichiers…), l’API select, disponible sur pratiquement toutes les plateformes. L’idée est donc que tu lui passes plusieurs sockets et que ton programme se fasse réveiller au bon moment. D’autres API plus récentes s’appellent poll, epoll (sous Linux) ou kqueue (sous FreeBSD), mais elles sont d’abord utilisées en C et ne sont pas disponibles sous toutes les plateformes.

asyncio est une surcouche par-dessus ces différentes API qui te permettra d’utiliser ce qu’on appelle des coroutines : des fonctions qui te permettront d’avoir un programme avec un seul fil d’exécution et un seul état, mais chaque fonction s’endormira individuellement et repassera la main à la boucle évènementielle quand tu auras un appel bloquant (lecture, écriture… sur par exemple un socket) à faire. Ces appels sont préfixés par le mot-clef await dans les versions récentes Python, et les définitions de coroutines sont préfixées par le mot-clef async.

Les coroutines sont une des différentes approches pour faire ce qu’on appelle de l’asynchrone (intégrer proprement une boucle événementielle à ton programme). Une autre approche est celle des callbacks, utilisée par Node.JS (une autre fonction, en général imbriquée dans la première, sera appelée « à retardement » quand la boucle évènementielle aura retourné quelque chose). Il y a aussi des bibliothèques qui t’encouragent à faire des callbacks en Python, mais c’est moins fréquent.

Dans tous les cas, le module select est peu utilisé car c’est principalement une « brique » qui te permet d’accéder à une API bas-niveau, pour des besoins bas-niveau.

Bonne journée,

+1 -0

Pour utiliser un chaque cœur d’un processeur multi-cœur, il faut soit lancer plusieurs programmes au niveau du système d’exploitation, soit lancer plusieurs threads au niveau d’un même programme.

r0anne

À relativiser cependant en Python, puisqu’actuellement avec le GIL même si deux threads s’exécutent sur deux cœurs différents, ils ne le feront jamais simultanément.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte