web-dev-qa-db-fra.com

Qu'est-ce que le «stockage local de threads» en Python et pourquoi en ai-je besoin?

Dans Python en particulier, comment les variables sont-elles partagées entre les threads?

Bien que j'aie utilisé threading.Thread avant je n'ai jamais vraiment compris ou vu des exemples de partage de variables. Sont-ils partagés entre le fil principal et les enfants ou seulement entre les enfants? Quand aurais-je besoin d'utiliser le stockage local des threads pour éviter ce partage?

J'ai vu de nombreux avertissements concernant la synchronisation de l'accès aux données partagées entre les threads à l'aide de verrous, mais je n'ai pas encore vu un très bon exemple du problème.

Merci d'avance!

87
Mike

En Python, tout est partagé, à l'exception des variables fonctionnelles locales (car chaque appel de fonction obtient son propre ensemble de paramètres locaux et les threads sont toujours des appels de fonction distincts.) Et même dans ce cas, seules les variables elles-mêmes (les noms qui se réfèrent aux objets) sont locales à la fonction; les objets eux-mêmes sont toujours globaux et tout peut s'y référer. L'objet Thread pour un thread particulier n'est pas un objet spécial à cet égard. Si vous stockez l'objet Thread quelque part où tous les threads peuvent accéder (comme une variable globale), alors tous les threads peuvent accéder à cet objet Thread. Si vous voulez modifier atomiquement tout que vous n'avez pas simplement créé dans ce même thread, et que vous n'avez pas enregistré n'importe où un autre thread peut y accéder, vous devez le protéger par une serrure. Et tous les threads doivent bien sûr partager ce même verrou, sinon ce ne serait pas très efficace.

Si vous voulez un stockage local de thread réel, c'est là que threading.local entre en jeu. Attributs de threading.local ne sont pas partagés entre les threads; chaque thread ne voit que les attributs qu'il y place lui-même. Si vous êtes curieux de son implémentation, la source est dans _ threading_local.py dans la bibliothèque standard.

74
Thomas Wouters

Considérez le code suivant:

#/usr/bin/env python

from time import sleep
from random import random
from threading import Thread, local

data = local()

def bar():
    print("I'm called from", data.v)

def foo():
    bar()

class T(Thread):
    def run(self):
        sleep(random())
        data.v = self.getName()   # Thread-1 and Thread-2 accordingly
        sleep(1)
        foo()
 >> T (). Start (); T (). Start () 
 Je suis appelé depuis Thread-2 
 Je suis appelé depuis Thread-1 

Ici, threading.local () est utilisé comme un moyen rapide et sale de passer certaines données de run () à bar () sans changer l'interface de foo ().

Notez que l'utilisation de variables globales ne fera pas l'affaire:

#/usr/bin/env python

from time import sleep
from random import random
from threading import Thread

def bar():
    global v
    print("I'm called from", v)

def foo():
    bar()

class T(Thread):
    def run(self):
        global v
        sleep(random())
        v = self.getName()   # Thread-1 and Thread-2 accordingly
        sleep(1)
        foo()
 >> T (). Start (); T (). Start () 
 Je suis appelé depuis Thread-2 
 Je suis appelé depuis Thread-2 

En attendant, si vous pouviez vous permettre de transmettre ces données comme argument de foo () - ce serait une manière plus élégante et bien conçue:

from threading import Thread

def bar(v):
    print("I'm called from", v)

def foo(v):
    bar(v)

class T(Thread):
    def run(self):
        foo(self.getName())

Mais cela n'est pas toujours possible lors de l'utilisation de code tiers ou mal conçu.

65
ahatchkins

Vous pouvez créer un stockage local de threads à l'aide de threading.local().

>>> tls = threading.local()
>>> tls.x = 4 
>>> tls.x
4

Les données stockées dans les tls seront uniques à chaque thread, ce qui contribuera à éviter tout partage involontaire.

17
Aaron Maenpaa

Comme dans toutes les autres langues, chaque thread de Python a accès aux mêmes variables. Il n'y a pas de distinction entre le "thread principal" et les threads enfants.

Une différence avec Python est que le Global Interpreter Lock signifie qu'un seul thread peut être en cours d'exécution Python code à la fois. Ce n'est pas très utile quand il vient à synchroniser l'accès, car tous les problèmes de préemption habituels s'appliquent toujours, et vous devez utiliser des primitives de threading comme dans d'autres langues. Cela signifie que vous devez reconsidérer si vous utilisiez des threads pour les performances, cependant.

2
Nick Johnson