Mis a jour le 2025-04-14, 12:10

multi-processing

On peut utiliser un simple fork (ne marche pas sous windows) :
  • os.fork() renvoie le PID de l'enfant chez le parent et 0 chez l'enfant.
  • il ne faut pas oublier de faire un waitpid sur le process :
    • os.waitpid(myPid, 0) : pour attendre un process fils particulier.
    • os.waitpid(-1, 0) : pour attendre n'importe quel process fils.
    • os.waitpid(-1, os.WNOHANG) : pour collecter n'importe quel process fils, mais sans bloquer.
    • une exception OSError est retournée si aucun process fils à attendre.
    la valeur retournée est alors une paire (pid, status) avec le pid du process terminé (0 si aucun) et le statut d'erreur (0 si aucune)
  • Exemple :
    for i in range(3):
        pid = os.fork()
        if pid != 0:
            # I am the parent
            pids.add(pid)
        else:
            # I am the child
            ...
            sys.exit(0)
    
    while True:
        # in the parent
        print('waiting for children')
        try:
            (pid, status) = os.waitpid(-1, os.WNOHANG)
        except OSError:
            break
        
Package multiprocessing : permet de faire la même chose que fork, mais portable (marche sous windows) et plus évolué :
  • principe est de démarrer de définir des process, les démarrer, et faire un join dessus quand ils ont fini :
        from multiprocessing import Process
        p = Process(target = myFunc, args = (i, ))
        p.start()
        p.join()
        
  • p.join(0) : ne bloque pas si le process n'est pas encore fini (mais il faudra recommencer). L'argument est en fait le timeout en secondes.
  • une fois qu'on a fait le join, faire if p.is_alive() pour savoir si le process est toujours vivant
  • p.exitcode : le statut de retour quand le process est fini (0 si ok).
  • on peut échanger des objets entre les sous-process et le process avec une queue (mais attention, il faut que ces données soient sérialisables) :
    from multiprocessing import Process, Queue
    def myFunc(q):
        d = {'a': 1, 'b': 3}
        q.put(d)
        l = [4, 6, 8]
        q.put(l)
    
    q = Queue()
    p = Process(target = myFunc, args = (q,))
    p.start()
    while True:
      try:
        # Le False évite de bloquer
        result = queue.get(False)
      except: Exception:
        # si rien à lire dans la queue, exception déclenchée.
        pass
      # join, sans bloquer
      p.join(0)
      if not p.is_alive():
        # Le process est terminé
        print(p.exitcode)
        print(result)
        break
      time.sleep(1)
        
    attention : si on ne lit pas la queue, le processus fils ne peut pas se terminer, et il va y avoir un deadlock.
    attention : si on met des objets trop gros dans la queue, elle peut trop se remplir et le put() bloque, ce qui bloque le process fils.
  • pipe pour la communication entre les process :
    from multiprocessing import Process, Pipe
    def myFunc(conn):
        conn.send('some text')
        print('in child', conn.recv())
    
    (parentConn, childConn) = Pipe()
    p = Process(target = myFunc, args = (childConn,))
    p.start()
    print('in parent', parentConn.recv())
    parentConn.send('other text')
    p.join()
        
Multithreading versus multiprocessing :
  • quand on fait du multi-threading en python, 2 threads ne peuvent pas exécuter du code python en même temps à cause d'un Global Interpreter Lock ! Le code ne sera accéléré que si beaucoup d'IO, si portions appelées font appel à du code extérieur ou à du code C (comme les applications avec numpy). Donc du calcul pure en python ne va pas plus vite avec des threads !!!
  • quand on fait du multi-processing, c'est plus lourd, car il faut créer des process, et la communication entre process est plus difficile car ils ne partagent pas la mémoire.
Multithreading :
  • Exemple :
    import threading
    def runIt(x):
        ...
    
    threads = []
    for i in [1, 2]:
        thread = threading.Thread(target = runIt, args = ('thread' + str(i), ))
        threads.append(thread)
        thread.start()
    for thread in threads:
            thread.join() # Attend que le thread soit fini
        
  • on peut donner un timeout à join, qui peut être 0 si on ne veut pas attendre : thread.join(0)
  • thread.isAlive() : teste si le thread vit encore.
  • la fonction appelée semble voir la même chose qu'une fonction quelconque (par exemple, variables globales sont vues).

Copyright python-simple.com
programmer en python, tutoriel python, graphes en python, Aymeric Duclert