Maison >développement back-end >Tutoriel Python >Comprendre les threads en Python
Nous verrons quelques exemples d'utilisation des threads en Python et comment éviter la concurrence des threads.
Vous devez exécuter l'exemple suivant plusieurs fois afin de remarquer que les threads sont imprévisibles et qu'ils produisent des résultats différents à chaque exécution. Avis de non-responsabilité : oubliez ce que vous avez entendu sur le GIL à partir de maintenant, car le GIL n'affecte pas ce que j'essaie de montrer.
Nous allons demander cinq URL différentes :
Fil unique
import time import urllib2 def get_responses(): urls = [ 'http://www.google.com', 'http://www.amazon.com', 'http://www.ebay.com', 'http://www.alibaba.com', 'http://www.reddit.com' ] start = time.time() for url in urls: print url resp = urllib2.urlopen(url) print resp.getcode() print "Elapsed time: %s" % (time.time()-start) get_responses()
Le résultat est :
http://www.google.com 200 http://www.amazon.com 200 http://www.ebay.com 200 http://www.alibaba.com 200 http://www.reddit.com 200 Elapsed time: 3.0814409256
Explication :
La commande d'url est demandée
À moins que le processeur reçoive une réponse d'une URL, il ne demandera pas l'URL suivante
Les requêtes réseau prennent beaucoup de temps, le CPU reste donc inactif en attendant le retour de la requête réseau.
Multi-threading
import urllib2 import time from threading import Thread class GetUrlThread(Thread): def __init__(self, url): self.url = url super(GetUrlThread, self).__init__() def run(self): resp = urllib2.urlopen(self.url) print self.url, resp.getcode() def get_responses(): urls = [ 'http://www.google.com', 'http://www.amazon.com', 'http://www.ebay.com', 'http://www.alibaba.com', 'http://www.reddit.com' ] start = time.time() threads = [] for url in urls: t = GetUrlThread(url) threads.append(t) t.start() for t in threads: t.join() print "Elapsed time: %s" % (time.time()-start) get_responses()
Sortie :
http://www.reddit.com 200 http://www.google.com 200 http://www.amazon.com 200 http://www.alibaba.com 200 http://www.ebay.com 200 Elapsed time: 0.689890861511
Explication :
Réalisation de l'amélioration du temps d'exécution du programme
Nous avons écrit un programme multithread pour réduire le temps d'attente du CPU Lorsque nous attendons le retour d'une requête réseau dans un thread, le CPU peut basculer vers d'autres threads pour effectuer des requêtes réseau dans d'autres threads.
Nous nous attendons à ce qu'un thread traite une URL, nous transmettons donc une URL lors de l'instanciation de la classe de thread.
L'exécution d'un thread signifie exécuter la méthode run()
dans la classe.
Cependant, nous voulons que chaque thread s'exécute run()
.
Créez un fil de discussion pour chaque URL et appelez la méthode start()
, qui indique au processeur d'exécuter la méthode run()
dans le fil.
Nous voulons calculer le temps passé lorsque tous les threads ont fini de s'exécuter, nous appelons donc la méthode join()
.
join()
Vous pouvez demander au thread principal d'attendre la fin de ce thread avant d'exécuter l'instruction suivante.
Nous appelons la méthode join()
pour chaque thread, nous calculons donc le temps d'exécution une fois que tous les threads ont terminé leur exécution.
À propos du fil :
Le CPU peut ne pas exécuter la méthode start()
immédiatement après avoir appelé run()
.
Vous ne pouvez pas déterminer l'ordre d'exécution de run()
entre différents threads.
Pour un seul thread, il est garanti que les instructions de la méthode run()
sont exécutées dans l'ordre.
En effet, l'URL du fil de discussion sera demandée en premier, puis le résultat renvoyé sera imprimé.
Nous utiliserons un programme pour démontrer la concurrence des ressources entre plusieurs threads et résoudre ce problème.
from threading import Thread #define a global variable some_var = 0 class IncrementThread(Thread): def run(self): #we want to read a global variable #and then increment it global some_var read_value = some_var print "some_var in %s is %d" % (self.name, read_value) some_var = read_value + 1 print "some_var in %s after increment is %d" % (self.name, some_var) def use_increment_thread(): threads = [] for i in range(50): t = IncrementThread() threads.append(t) t.start() for t in threads: t.join() print "After 50 modifications, some_var should have become 50" print "After 50 modifications, some_var is %d" % (some_var,) use_increment_thread()
Exécutez ce programme plusieurs fois et vous verrez une variété de résultats différents.
Explication :
Il existe une variable globale que tous les threads souhaitent modifier.
Tous les threads doivent ajouter 1 à cette variable globale.
Avec 50 threads, la valeur finale devrait devenir 50, mais ce n'est pas le cas.
Pourquoi n’a-t-il pas atteint 50 ?
Lorsque some_var
est 15
, le thread t1
lit some_var
, et à ce moment le CPU donne le contrôle à un autre thread t2
.
t2
Le some_var
lu par le fil est aussi 15
t1
et t2
ajoutent some_var
à 16
Ce à quoi nous nous attendions à ce moment-là, c'était que t1
t2
deux fils feraient some_var + 2
devenir 17
Il y a ici une concurrence pour les ressources.
La même situation peut également se produire dans d'autres discussions, le résultat final peut donc être inférieur à 50
.
Résoudre la concurrence des ressources
from threading import Lock, Thread lock = Lock() some_var = 0 class IncrementThread(Thread): def run(self): #we want to read a global variable #and then increment it global some_var lock.acquire() read_value = some_var print "some_var in %s is %d" % (self.name, read_value) some_var = read_value + 1 print "some_var in %s after increment is %d" % (self.name, some_var) lock.release() def use_increment_thread(): threads = [] for i in range(50): t = IncrementThread() threads.append(t) t.start() for t in threads: t.join() print "After 50 modifications, some_var should have become 50" print "After 50 modifications, some_var is %d" % (some_var,) use_increment_thread()
La mise en œuvre de ce programme a de nouveau permis d'obtenir les résultats que nous attendions.
Explication :
Le verrouillage est utilisé pour empêcher les conditions de course
Si le thread t1
acquiert le verrou avant d'effectuer certaines opérations. Les autres threads n'effectueront pas la même opération t1
avant que
我们想要确定的是一旦线程t1
已经读取了some_var
,直到t1
完成了修改some_var
,其他的线程才可以读取some_var
这样读取和修改some_var
成了逻辑上的原子操作。
让我们用一个例子来证明一个线程不能影响其他线程内的变量(非全局变量)。
time.sleep()可以使一个线程挂起,强制线程切换发生。
from threading import Thread import time class CreateListThread(Thread): def run(self): self.entries = [] for i in range(10): time.sleep(1) self.entries.append(i) print self.entries def use_create_list_thread(): for i in range(3): t = CreateListThread() t.start() use_create_list_thread()
运行几次后发现并没有打印出争取的结果。当一个线程正在打印的时候,cpu切换到了另一个线程,所以产生了不正确的结果。我们需要确保print self.entries
是个逻辑上的原子操作,以防打印时被其他线程打断。
我们使用了Lock(),来看下边的例子。
from threading import Thread, Lock import time lock = Lock() class CreateListThread(Thread): def run(self): self.entries = [] for i in range(10): time.sleep(1) self.entries.append(i) lock.acquire() print self.entries lock.release() def use_create_list_thread(): for i in range(3): t = CreateListThread() t.start() use_create_list_thread()
这次我们看到了正确的结果。证明了一个线程不可以修改其他线程内部的变量(非全局变量)。
原文出处: Akshar Raaj
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!