Che cos'è la "memoria locale del thread" in Python e perché ne ho bisogno?


100

In Python in particolare, come vengono condivise le variabili tra i thread?

Anche se l'ho usato threading.Threadprima, non ho mai capito o visto esempi di come le variabili fossero condivise. Sono condivisi tra il filo principale e i bambini o solo tra i bambini? Quando dovrei utilizzare l'archiviazione locale del thread per evitare questa condivisione?

Ho visto molti avvisi sulla sincronizzazione dell'accesso ai dati condivisi tra i thread utilizzando i blocchi, ma devo ancora vedere un ottimo esempio del problema.

Grazie in anticipo!


2
Il titolo non corrisponde alla domanda. La domanda ha a che fare con la condivisione di variabili tra i thread, il titolo implica che si tratta specificamente di archiviazione locale dei thread
Casebash

2
@Casebash: dal suono di questa domanda, Mike ha letto che TLS è necessario per evitare i problemi causati dai dati condivisi, ma non era chiaro quali dati fossero condivisi per impostazione predefinita, con cosa fossero condivisi e come fossero condivisi. Ho adattato il titolo per adattarlo meglio alla domanda.
Shog9,

Risposte:


83

In Python, tutto è condiviso, ad eccezione delle variabili locali di funzione (perché ogni chiamata di funzione ottiene il proprio insieme di variabili locali e i thread sono sempre chiamate di funzioni separate). E anche in questo caso, solo le variabili stesse (i nomi che si riferiscono agli oggetti) sono locali rispetto alla funzione; gli oggetti stessi sono sempre globali e qualsiasi cosa può fare riferimento ad essi. L' Threadoggetto per un particolare thread non è un oggetto speciale a questo proposito. Se si memorizza l' Threadoggetto da qualche parte a cui possono accedere tutti i thread (come una variabile globale), tutti i thread possono accedere a Threadquell'oggetto. Se vuoi modificare atomicamente qualsiasi cosa a cui ha accesso un altro thread, devi proteggerlo con un lucchetto. E tutti i thread devono ovviamente condividere lo stesso blocco, altrimenti non sarebbe molto efficace.

Se si desidera l'archiviazione locale del thread, è qui che threading.localentra in gioco. Gli attributi di threading.localnon sono condivisi tra i thread; ogni thread vede solo gli attributi che esso stesso ha inserito lì dentro. Se sei curioso della sua implementazione, la fonte è in _threading_local.py nella libreria standard.


1
Puoi fornire maggiori dettagli sulla seguente frase, per favore? "Se vuoi modificare atomicamente qualcosa che non hai semplicemente creato in questo stesso thread e non hai archiviato dove un altro thread può raggiungerlo, devi proteggerlo con un lucchetto."
changyuheng

@changyuheng: Ecco una spiegazione di cosa sono le azioni atomiche: cs.nott.ac.uk/~psznza/G52CON/lecture4.pdf
Tom Busby

1
@TomBusby: se non ci sono altri thread che possono arrivarci, perché dobbiamo proteggerlo con un blocco, cioè perché dobbiamo rendere il processo atomico?
changyuheng

2
Per favore, puoi fare un rapido esempio di: "gli oggetti stessi sono sempre globali e qualsiasi cosa può fare riferimento a loro". Per refer, supponi che intendi leggere e non assegnare / aggiungere?
variabile

@variable: Penso che significhi che i valori non hanno ambito
user1071847

75

Considera il codice seguente:

#/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 ()
Sono chiamato da Thread-2
Sono chiamato da Thread-1 

Qui threading.local () è usato come un modo rapido e sporco per passare alcuni dati da run () a bar () senza cambiare l'interfaccia di foo ().

Nota che l'utilizzo di variabili globali non farà il trucco:

#/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 ()
Sono chiamato da Thread-2
Sono chiamato da Thread-2 

Nel frattempo, se potessi permetterti di passare questi dati come argomento di foo (), sarebbe un modo più elegante e ben progettato:

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

Ma questo non è sempre possibile quando si utilizza codice di terze parti o mal progettato.


18

Puoi creare l'archiviazione locale del thread utilizzando threading.local().

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

I dati memorizzati in tls saranno univoci per ogni thread, il che aiuterà a garantire che non si verifichi una condivisione involontaria.


2

Proprio come in ogni altra lingua, ogni thread in Python ha accesso alle stesse variabili. Non c'è distinzione tra il "thread principale" e i thread secondari.

Una differenza con Python è che Global Interpreter Lock significa che solo un thread può eseguire il codice Python alla volta. Questo non è di grande aiuto quando si tratta di sincronizzare l'accesso, tuttavia, poiché tutti i soliti problemi di prelazione si applicano ancora, e devi usare le primitive di threading proprio come in altri linguaggi. Significa che devi riconsiderare se stavi usando i thread per le prestazioni, tuttavia.


0

Potrei sbagliarmi qui. Se sai diversamente, spiega perché questo aiuterebbe a spiegare perché è necessario utilizzare thread local ().

Questa affermazione sembra fuori luogo, non sbagliata: "Se vuoi modificare atomicamente qualsiasi cosa a cui ha accesso un altro thread, devi proteggerlo con un lucchetto." Penso che questa affermazione sia -> effettivamente <- giusta ma non del tutto accurata. Ho pensato che il termine "atomico" significasse che l'interprete Python ha creato un blocco di codice byte che non lasciava spazio per un segnale di interrupt alla CPU.

Pensavo che le operazioni atomiche fossero blocchi di byte code Python che non danno accesso agli interrupt. Dichiarazioni Python come "running = True" sono atomiche. Non è necessario bloccare la CPU dagli interrupt in questo caso (credo). La scomposizione del codice byte Python è al sicuro dall'interruzione del thread.

Il codice Python come "thread_running [5] = True" non è atomico. Ci sono due blocchi di codice byte Python qui; uno per de-referenziare list () per un oggetto e un altro blocco di codice byte per assegnare un valore a un oggetto, in questo caso un "posto" in una lista. Può essere generato un interrupt -> tra <- i due byte-code -> blocchi <-. Ecco dove accadono cose brutte.

In che modo thread local () si relaziona a "atomic"? Questo è il motivo per cui l'affermazione mi sembra fuorviante. Se no puoi spiegare?


1
Questa sembra una risposta, ma è stata segnalata come problematica, presumo a causa delle domande poste. Eviterei di chiedere chiarimenti nella risposta. A questo servono i commenti.
Dharman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.