Was ist "Thread Local Storage" in Python und warum brauche ich es?

100

Wie werden in Python speziell Variablen zwischen Threads geteilt?

Obwohl ich zuvor verwendet habe, habe threading.Threadich nie wirklich verstanden oder Beispiele dafür gesehen, wie Variablen geteilt wurden. Werden sie zwischen dem Hauptthema und den Kindern oder nur unter den Kindern geteilt? Wann müsste ich lokalen Thread-Speicher verwenden, um diese Freigabe zu vermeiden?

Ich habe viele Warnungen bezüglich der Synchronisierung des Zugriffs auf gemeinsam genutzte Daten zwischen Threads mithilfe von Sperren gesehen, aber ich habe noch kein wirklich gutes Beispiel für das Problem gefunden.

Danke im Voraus!

Mike
quelle
2
Der Titel stimmt nicht mit der Frage überein. Die Frage hat mit der gemeinsamen Nutzung von Variablen zwischen Threads zu tun. Der Titel impliziert, dass es speziell um die lokale Speicherung von Threads geht
Casebash
2
@Casebash: Nach dem Klang dieser Frage las Mike, dass TLS notwendig ist, um die durch gemeinsam genutzte Daten verursachten Probleme zu vermeiden. Es war jedoch unklar, welche Daten standardmäßig gemeinsam genutzt wurden, mit welchen Daten sie geteilt wurden und wie sie gemeinsam genutzt wurden. Ich habe den Titel angepasst, um der Frage besser zu entsprechen.
Shog9

Antworten:

83

In Python wird alles gemeinsam genutzt, mit Ausnahme der funktionslokalen Variablen (da jeder Funktionsaufruf seine eigenen lokalen Elemente erhält und Threads immer separate Funktionsaufrufe sind.) Und selbst dann nur die Variablen selbst (die Namen, die sich auf Objekte beziehen). sind lokal für die Funktion; Objekte selbst sind immer global und alles kann auf sie verweisen. Das ThreadObjekt für einen bestimmten Thread ist in dieser Hinsicht kein spezielles Objekt. Wenn Sie das ThreadObjekt an einem Ort speichern, auf den alle Threads zugreifen können (wie bei einer globalen Variablen), können alle Threads auf dieses eine ThreadObjekt zugreifen . Wenn Sie etwas atomar ändern möchten, auf das ein anderer Thread Zugriff hat, müssen Sie ihn mit einer Sperre schützen. Und alle Threads müssen natürlich dieselbe Sperre haben, sonst wäre es nicht sehr effektiv.

Wenn Sie einen tatsächlichen threadlokalen Speicher möchten, threading.localkommt dies ins Spiel. Attribute von threading.localwerden nicht zwischen Threads geteilt. Jeder Thread sieht nur die Attribute, die er selbst dort platziert hat. Wenn Sie neugierig auf die Implementierung sind, befindet sich die Quelle in der Standardbibliothek in _threading_local.py .

Thomas Wouters
quelle
1
Können Sie bitte weitere Details zum folgenden Satz angeben? "Wenn Sie etwas atomar ändern möchten, das Sie nicht nur in demselben Thread erstellt und nirgendwo gespeichert haben, wo ein anderer Thread darauf zugreifen kann, müssen Sie es durch eine Sperre schützen."
Changyuheng
@changyuheng: Hier ist eine Erklärung, was atomare Aktionen sind: cs.nott.ac.uk/~psznza/G52CON/lecture4.pdf
Tom Busby
1
@ TomBusby: Wenn es keine anderen Threads gibt, die darauf zugreifen können, warum müssen wir es durch eine Sperre schützen, dh warum müssen wir den Prozess atomar machen?
Changyuheng
2
Bitte geben Sie ein kurzes Beispiel für: "Objekte selbst sind immer global und alles kann auf sie verweisen". Mit Verweisen meinen Sie lesen und nicht zuweisen / anhängen?
Variable
@variable: Ich denke, er meint, Werte haben keinen Gültigkeitsbereich
user1071847
75

Betrachten Sie den folgenden Code:

#/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 ()
Ich werde von Thread-2 angerufen
Ich werde von Thread-1 angerufen 

Hier wird threading.local () als schnelle und schmutzige Methode verwendet, um einige Daten von run () an bar () zu übergeben, ohne die Schnittstelle von foo () zu ändern.

Beachten Sie, dass die Verwendung globaler Variablen nicht ausreicht:

#/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 ()
Ich werde von Thread-2 angerufen
Ich werde von Thread-2 angerufen 

Wenn Sie es sich leisten könnten, diese Daten als Argument von foo () weiterzugeben, wäre dies eine elegantere und besser gestaltete Methode:

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())

Dies ist jedoch nicht immer möglich, wenn Sie Code von Drittanbietern oder schlecht gestalteten verwenden.

Ahatchkins
quelle
18

Sie können einen lokalen Thread-Speicher mit erstellen threading.local().

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

In den TLS gespeicherte Daten sind für jeden Thread eindeutig, wodurch sichergestellt wird, dass keine unbeabsichtigte Freigabe erfolgt.

Aaron Maenpaa
quelle
2

Wie in jeder anderen Sprache hat jeder Thread in Python Zugriff auf dieselben Variablen. Es gibt keinen Unterschied zwischen dem Hauptthread und den untergeordneten Threads.

Ein Unterschied zu Python besteht darin, dass die globale Interpreter-Sperre bedeutet, dass jeweils nur ein Thread Python-Code ausführen kann. Dies ist jedoch keine große Hilfe, wenn es um die Synchronisierung des Zugriffs geht, da immer noch alle üblichen Vorkaufsprobleme auftreten und Sie Threading-Grundelemente wie in anderen Sprachen verwenden müssen. Es bedeutet jedoch, dass Sie erneut überlegen müssen, ob Sie Threads für die Leistung verwendet haben.

Nick Johnson
quelle
0

Ich kann mich hier irren. Wenn Sie etwas anderes wissen, erläutern Sie dies bitte, da dies erklären würde, warum Thread local () verwendet werden muss.

Diese Aussage scheint nicht falsch zu sein: "Wenn Sie etwas atomar ändern möchten, auf das ein anderer Thread Zugriff hat, müssen Sie es mit einer Sperre schützen." Ich denke, diese Aussage ist -> effektiv <- richtig, aber nicht ganz richtig. Ich dachte, der Begriff "atomar" bedeutete, dass der Python-Interpreter einen Bytecode-Block erstellte, der keinen Platz für ein Interrupt-Signal an die CPU ließ.

Ich dachte, atomare Operationen sind Teile des Python-Bytecodes, die keinen Zugriff auf Interrupts gewähren. Python-Anweisungen wie "running = True" sind atomar. In diesem Fall müssen Sie die CPU nicht vor Interrupts sperren (glaube ich). Die Aufschlüsselung des Python-Bytecodes ist vor Thread-Unterbrechungen geschützt.

Python-Code wie "threads_running [5] = True" ist nicht atomar. Hier gibt es zwei Teile des Python-Bytecodes. eine, um die Liste () für ein Objekt zu de-referenzieren, und eine andere Bytecode-Block, um einem Objekt einen Wert zuzuweisen, in diesem Fall eine "Stelle" in einer Liste. Ein Interrupt kann ausgelöst werden -> zwischen <- den beiden Byte-Code -> Chunks <-. Dort passieren schlimme Dinge.

In welcher Beziehung steht thread local () zu "atomar"? Deshalb scheint mir die Aussage falsch zu sein. Wenn nicht, können Sie das erklären?

DevPlayer
quelle
1
Dies sieht aus wie eine Antwort, wurde aber aufgrund der gestellten Fragen als problematisch gemeldet. Ich würde es vermeiden, in der Antwort um Klarstellung zu bitten. Dafür sind Kommentare da.
Dharman