Comment utiliser le threading en Python ?

J'essaie de comprendre le threading en Python. J'ai regardé la documentation et les exemples, mais franchement, de nombreux exemples sont trop sophistiqués et j'ai du mal à les comprendre.

Comment montrer clairement que les tâches sont divisées pour le multithreading ?

Voici un exemple simple : vous devez essayer quelques URL alternatives et renvoyer le contenu de la première qui répond.

import Queue
import threading
import urllib2

# called by each thread
def get_url(q, url):
    q.put(urllib2.urlopen(url).read())

theurls = ["http://google.com", "http://yahoo.com"]

q = Queue.Queue()

for u in theurls:
    t = threading.Thread(target=get_url, args = (q,u))
    t.daemon = True
    t.start()

s = q.get()
print s

Il s'agit d'un cas où le threading est utilisé comme une simple optimisation : chaque sous-fil attend qu'une URL soit résolue et réponde, afin de mettre son contenu dans la file d'attente ; chaque fil est un démon (il ne maintiendra pas le processus si le fil principal s'arrête -- ce qui est plus fréquent que le contraire) ; le fil principal lance tous les sous-fils, fait un get sur la file d'attente pour attendre que l'un d'eux ait fait un put, puis émet les résultats et se termine (ce qui arrête tous les sous-fils qui pourraient encore être en cours d'exécution, puisqu'ils sont des fils démons).

L'utilisation correcte des threads en Python est invariablement liée aux opérations d'E/S (puisque CPython n'utilise pas plusieurs coeurs pour exécuter des tâches liées au CPU de toute façon, la seule raison d'utiliser les threads est de ne pas bloquer le processus pendant l'attente d'une E/S). Les files d'attente sont presque invariablement le meilleur moyen de répartir le travail entre les threads et/ou de collecter les résultats du travail, et elles sont intrinsèquement threadsafe, ce qui vous évite de vous soucier des verrous, des conditions, des événements, des sémaphores et d'autres concepts de coordination/communication inter-threads.

Commentaires (14)

NOTE : Pour une parallélisation réelle en Python, vous devez utiliser le module multiprocessing pour forker plusieurs processus qui s'exécutent en parallèle (en raison du verrouillage global de l'interpréteur, les threads Python fournissent un entrelacement mais sont en fait exécutés en série, pas en parallèle, et ne sont utiles que pour entrelacer les opérations d'E/S).

Toutefois, si vous recherchez simplement l'entrelacement (ou si vous effectuez des opérations d'E/S qui peuvent être parallélisées malgré le verrouillage global de l'interpréteur), le module threading est l'endroit où commencer. À titre d'exemple vraiment simple, considérons le problème de la sommation d'une grande plage en additionnant des sous-périodes en parallèle :

import threading

class SummingThread(threading.Thread):
     def __init__(self,low,high):
         super(SummingThread, self).__init__()
         self.low=low
         self.high=high
         self.total=0

     def run(self):
         for i in range(self.low,self.high):
             self.total+=i

thread1 = SummingThread(0,500000)
thread2 = SummingThread(500000,1000000)
thread1.start() # This actually causes the thread to run
thread2.start()
thread1.join()  # This waits until the thread has completed
thread2.join()  
# At this point, both threads have completed
result = thread1.total + thread2.total
print result

Notez que l'exemple ci-dessus est très stupide, car il ne fait absolument aucune E/S et sera exécuté en série bien qu'entrelacé (avec la surcharge supplémentaire du changement de contexte) dans CPython en raison du verrouillage global de l'interpréteur.

Commentaires (8)

Comme d'autres l'ont mentionné, CPython ne peut utiliser les threads que pour les attentes I\O en raison de la GIL. Si vous souhaitez bénéficier de plusieurs cœurs pour les tâches liées au CPU, utilisez multiprocessing :

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    p = Process(target=f, args=('bob',))
    p.start()
    p.join()
Commentaires (5)