ProgrammationDéveloppeur Python

Que se passe-t-il lors du transfert d'objets composés (par exemple, une liste de dictionnaires) entre des threads ou des processus en Python ? Qu'est-il important de garder à l'esprit lors de la programmation d'applications multithread et multiprocesseur liées à des collections modifiables ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

En Python, lors du travail avec des threads, les objets en mémoire sont accessibles à tous les threads sans copie. Cependant, lors du travail avec des processus à travers multiprocessing, les objets Python standards (par exemple, listes, dictionnaires) sont transférés entre les processus par sérialisation (généralement via pickle), et chaque processus reçoit sa propre copie de l'objet. Une modification de l'objet dans un processus ne se reflète pas dans la copie d'un autre processus.

Principales différences :

  • Multithreading (threading) : Tous les threads fonctionnent dans un espace d'adressage commun. Tous les objets (y compris les collections imbriquées) sont partagés.
  • Multiprocessing (multiprocessing) : Chaque processus a sa propre mémoire ; les objets sont transférés par sérialisation. Des structures spéciales doivent être utilisées pour l'échange entre les processus, par exemple, multiprocessing.Manager().dict().

Exemple :

# Multiprocessing from multiprocessing import Process, Manager def worker(d): d['a'] = 42 if __name__ == '__main__': # Un dict simple ne fonctionne pas entre les processus managed_dict = Manager().dict() p = Process(target=worker, args=(managed_dict,)) p.start(); p.join() print(managed_dict['a']) # 42
  • Si un dict ordinaire avait été utilisé, les modifications dans le processus n'auraient pas été visibles dans le processus principal.

Question piège.

Dans quels cas la modification d'une liste ordinaire (list) dans un processus se reflétera-t-elle dans un autre processus ?

Réponse :

Jamais, si vous utilisez multiprocessing. Les objets Python ordinaires ne sont pas partagés entre les processus. Pour partager, il faut utiliser des primitives spéciales — des objets Manager (par exemple, Manager().list(), Manager().dict()), qui synchronisent l'état entre les processus.

Exemples d'erreurs réelles dues à l'ignorance des subtilités du sujet.


Histoire

L'équipe de développeurs d'un crawler web a initialisé les files d'attente de tâches comme des listes et les a partagées entre les processus via multiprocessing. Les tâches "se perdaient" car les processus ne voyaient pas les mises à jour de chacun. Ils ont corrigé cela en utilisant Manager().list().


Histoire

Dans un service analytique, les logs étaient tentés d'être collectés dans des dict imbriqués à l'intérieur des threads. Dans un scénario à forte charge, cela a conduit à des conditions de course et à la corruption des données en raison de l'absence de verrouillages.


Histoire

Dans un service ETL, lors de la tentative de rassembler les objets de données collectés pendant la phase de traitement dans plusieurs processus, ils ont découvert des duplications/pertes de données : les programmeurs ne prenaient pas en compte que chaque processus travaillait avec sa propre copie de la structure, et non avec une commune.