Maison >développement back-end >Tutoriel Python >Comprendre les threads en Python

Comprendre les threads en Python

巴扎黑
巴扎黑original
2017-04-30 14:38:281249parcourir

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.

Exemple 1

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é.

Exemple 2

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.


  • t2Le 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

    libère le verrou
  • 我们想要确定的是一旦线程t1已经读取了some_var,直到t1完成了修改some_var,其他的线程才可以读取some_var


  • 这样读取和修改some_var成了逻辑上的原子操作。

  实例3

  让我们用一个例子来证明一个线程不能影响其他线程内的变量(非全局变量)。

  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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn