2008-09-19 12 views
73

En Python específicamente, ¿cómo se comparten las variables entre los hilos?¿Qué es "almacenamiento local de subprocesos" en Python, y por qué lo necesito?

Aunque he usado threading.Thread antes nunca entendí o vi ejemplos de cómo se compartieron las variables. ¿Se comparten entre el hilo principal y los niños o solo entre los niños? ¿Cuándo necesitaré usar el almacenamiento local de subprocesos para evitar compartir?

He visto muchas advertencias sobre la sincronización de acceso a datos compartidos entre hilos mediante el uso de bloqueos, pero todavía no he visto un buen ejemplo del problema.

¡Gracias de antemano!

+1

El título no coincide con la pregunta. La cuestión es compartir variables entre hilos, el título implica que es específicamente sobre el almacenamiento local de subprocesos – Casebash

+1

@Casebash: a partir del sonido de esta pregunta, Mike leyó que TLS es necesario para evitar los problemas causados ​​por los datos compartidos, pero era No está claro qué datos se compartieron por defecto, con qué se compartió y cómo se compartió. He ajustado el título para que coincida mejor con la pregunta. – Shog9

Respuesta

57

En Python, todo se comparte, excepto las variables de función local (porque cada llamada de función obtiene su propio conjunto de locales, y los hilos son siempre llamadas de función separadas). Y aun así, solo las variables mismas (los nombres que referirse a objetos) son locales a la función; los objetos mismos son siempre globales, y cualquier cosa puede referirse a ellos. El objeto Thread para una hebra en particular no es un objeto especial a este respecto. Si almacena el objeto Thread en alguna parte a la que todos los hilos puedan acceder (como una variable global), entonces todos los hilos pueden acceder a ese único objeto Thread. Si quiere modificar atómicamente cualquier cosa que no acaba de crear en este mismo hilo, y no almacenó en cualquier lugar que otro hilo pueda obtener, tiene que protegerlo con un candado. Y todos los hilos deben, por supuesto, compartir este mismo bloqueo, o no sería muy efectivo.

Si desea un almacenamiento de hilo local real, allí es donde entra threading.local. Los atributos de threading.local no se comparten entre subprocesos; cada hilo ve solo los atributos que él mismo colocó allí. Si tiene curiosidad acerca de su implementación, la fuente está en _threading_local.py en la biblioteca estándar.

0

Al igual que en cualquier otro idioma, cada hilo en Python tiene acceso a las mismas variables. No hay distinción entre el 'hilo principal' y los hilos secundarios.

Una diferencia con Python es que el bloqueo de intérprete global significa que solo un hilo puede ejecutar código Python a la vez. Sin embargo, esto no es de mucha ayuda cuando se trata de sincronizar el acceso, ya que todos los problemas de prioridad preferentes se siguen aplicando, y debe usar primitivas de subprocesamiento como en otros idiomas. Sin embargo, significa que debe reconsiderar si estaba utilizando subprocesos para el rendimiento.

13

Puede crear el almacenamiento local de subprocesos usando threading.local().

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

datos almacenados en el TLS serán únicos para cada hilo que ayudará a asegurar que el intercambio no intencional no se produce.

53

Consideremos el siguiente código:

#/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() 
I'm called from Thread-2 
I'm called from Thread-1

Aquí threading.local() se utiliza como una manera rápida y sucia para pasar algunos datos de run() a la barra() sin cambiar la interfaz de foo().

Tenga en cuenta que el uso de variables globales no hará el truco:

#/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() 
I'm called from Thread-2 
I'm called from Thread-2

Mientras tanto, si usted puede permitirse pasar estos datos a través como un argumento de foo() - que sería una más elegante y forma bien diseñado:

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

Pero esto no siempre es posible cuando el uso de terceros o código mal diseñado.

Cuestiones relacionadas