threading - Enrutar el almacenamiento local en Python
threading python ejemplos (4)
También puede escribir
import threading
mydata = threading.local()
mydata.x = 1
mydata.x solo existirá en el hilo actual
¿Cómo uso el almacenamiento local de subprocesos en Python?
Relacionado
- ¿Qué es "almacenamiento local de subprocesos" en Python, y por qué lo necesito? - Este hilo parece centrarse más en cuándo se comparten las variables.
- Manera eficiente de determinar si una función en particular está en la pila en Python - Alex Martelli da una buena solución
El almacenamiento local de subprocesos simplemente puede considerarse como un espacio de nombres (con valores accedidos mediante la notación de atributos). La diferencia es que cada subproceso obtiene de forma transparente su propio conjunto de atributos / valores, de modo que un subproceso no vea los valores de otro subproceso.
Al igual que un objeto ordinario, puede crear múltiples instancias threading.local
en su código. Pueden ser variables locales, miembros de clase o instancia, o variables globales. Cada uno es un espacio de nombres separado.
Aquí hay un ejemplo simple:
import threading
class Worker(threading.Thread):
ns = threading.local()
def run(self):
self.ns.val = 0
for i in range(5):
self.ns.val += 1
print("Thread:", self.name, "value:", self.ns.val)
w1 = Worker()
w2 = Worker()
w1.start()
w2.start()
w1.join()
w2.join()
Salida:
Thread: Thread-1 value: 1
Thread: Thread-2 value: 1
Thread: Thread-1 value: 2
Thread: Thread-2 value: 2
Thread: Thread-1 value: 3
Thread: Thread-2 value: 3
Thread: Thread-1 value: 4
Thread: Thread-2 value: 4
Thread: Thread-1 value: 5
Thread: Thread-2 value: 5
Observe cómo cada subproceso mantiene su propio contador, aunque el atributo ns
sea un miembro de la clase (y, por lo tanto, compartido entre los subprocesos).
El mismo ejemplo podría haber usado una variable de instancia o una variable local, pero eso no mostraría mucho, ya que no hay intercambio entonces (un dict funcionaría igual de bien). Hay casos en los que necesitaría almacenamiento local de subprocesos como variables de instancia o variables locales, pero tienden a ser relativamente raros (y bastante sutiles).
El almacenamiento local de subprocesos es útil, por ejemplo, si tiene un grupo de trabajo de subprocesos y cada subproceso necesita acceso a su propio recurso, como una conexión de red o de base de datos. Tenga en cuenta que el módulo de threading
utiliza el concepto habitual de subprocesos (que tienen acceso a los datos globales de proceso), pero estos no son demasiado útiles debido al bloqueo de intérprete global. El diferente módulo de multiprocessing
crea un nuevo subproceso para cada uno, por lo que cualquier global será local.
módulo de roscado
Aquí hay un ejemplo simple:
import threading
from threading import current_thread
threadLocal = threading.local()
def hi():
initialized = getattr(threadLocal, ''initialized'', None)
if initialized is None:
print("Nice to meet you", current_thread().name)
threadLocal.initialized = True
else:
print("Welcome back", current_thread().name)
hi(); hi()
Esto se imprimirá:
Nice to meet you MainThread
Welcome back MainThread
Una cosa importante que se pasa por alto fácilmente: un objeto threading.local()
solo necesita crearse una vez, ni una vez por hilo ni una vez por llamada de función. El global
o de class
son ubicaciones ideales.
Aquí está el por qué: threading.local()
realidad crea una nueva instancia cada vez que se llama (al igual que cualquier fábrica o llamada de clase), por lo que llamar a threading.local()
varias veces sobrescribe constantemente el objeto original, lo que con toda probabilidad es no lo que uno quiere Cuando un hilo accede a una variable threadLocal
existente (o como se llame), obtiene su propia vista privada de esa variable.
Esto no funcionará según lo previsto:
import threading
from threading import current_thread
def wont_work():
threadLocal = threading.local() #oops, this creates a new dict each time!
initialized = getattr(threadLocal, ''initialized'', None)
if initialized is None:
print("First time for", current_thread().name)
threadLocal.initialized = True
else:
print("Welcome back", current_thread().name)
wont_work(); wont_work()
Producirá esta salida:
First time for MainThread
First time for MainThread
módulo de multiprocesamiento
Todas las variables globales son locales de subprocesos, ya que el módulo de multiprocessing
crea un nuevo proceso para cada subproceso.
Considere este ejemplo, donde el contador processed
es un ejemplo de almacenamiento local de subprocesos:
from multiprocessing import Pool
from random import random
from time import sleep
import os
processed=0
def f(x):
sleep(random())
global processed
processed += 1
print("Processed by %s: %s" % (os.getpid(), processed))
return x*x
if __name__ == ''__main__'':
pool = Pool(processes=4)
print(pool.map(f, range(10)))
Saldrá algo como esto:
Processed by 7636: 1
Processed by 9144: 1
Processed by 5252: 1
Processed by 7636: 2
Processed by 6248: 1
Processed by 5252: 2
Processed by 6248: 2
Processed by 9144: 2
Processed by 7636: 3
Processed by 5252: 3
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
... por supuesto, los identificadores de subprocesos y los recuentos para cada orden variarán de ejecución a ejecución.
Como se señaló en la pregunta, Alex Martelli da una solución aquí . Esta función nos permite usar una función de fábrica para generar un valor predeterminado para cada hilo.
#Code originally posted by Alex Martelli
#Modified to use standard Python variable name conventions
import threading
threadlocal = threading.local()
def threadlocal_var(varname, factory, *args, **kwargs):
v = getattr(threadlocal, varname, None)
if v is None:
v = factory(*args, **kwargs)
setattr(threadlocal, varname, v)
return v