2009-08-21 15 views
22

¿El acceso y el cambio de valores de diccionario son seguros para subprocesos?Uso de un diccionario global con subprocesos en Python

que tienen un diccionario mundial foo y múltiples hilos con los identificadores de id1, id2, ..., idn. ¿Está bien acceder y cambiar los valores de foo sin asignarle un bloqueo si se sabe que cada hilo solo funcionará con su valor relacionado con el id, por ejemplo, el hilo con id1 solo funcionará con foo[id1]?

+0

Usted ** está ** usando CPython, ¿verdad? – voyager

+0

@voyager: sí, estoy usando CPython. – Alex

Respuesta

34

Suponiendo CPython: Sí y no. De hecho, es seguro buscar/almacenar valores de un diccionario compartido en el sentido de que múltiples solicitudes concurrentes de lectura/escritura no dañarán el diccionario. Esto se debe al bloqueo de intérprete global ("GIL") mantenido por la implementación. Es decir:

Tema Una ejecución:

a = global_dict["foo"] 

Tema B en ejecución:

global_dict["bar"] = "hello" 

Tema acondicionado funcionando:

global_dict["baz"] = "world" 

voluntad no corromper el diccionario, incluso si todo tres intentos de acceso ocurren en el "mismo" momento. El intérprete los serializará de alguna manera indefinida.

Sin embargo, los resultados de la siguiente secuencia es indefinido:

Tema A:

if "foo" not in global_dict: 
    global_dict["foo"] = 1 

Tema B:

global_dict["foo"] = 2 

como la prueba/set en hilo A no es atómica (condición de carrera de "tiempo de verificación/tiempo de uso").Por lo tanto, generalmente es mejor, si se bloquea cosas:

lock = RLock() 

def thread_A(): 
    lock.acquire() 
    try: 
     if "foo" not in global_dict: 
      global_dict["foo"] = 1 
    finally: 
     lock.release() 

def thread_B(): 
    lock.acquire() 
    try: 
     global_dict["foo"] = 2 
    finally: 
     lock.release() 
+0

¿'global_dict.setdefault (" foo ", 1)' en 'Subproceso A' haría innecesaria la necesidad de un bloqueo? – Claudiu

+0

¿Entiendo esto correctamente? Siempre y cuando agregue al diccionario sin modificaciones, es seguro. es decir, dict ['a'] = 1 en el hilo a y dict ['b'] = 2 en el hilo b está bien porque las teclas a y b no son lo mismo? – Cripto

+0

@ user1048138 - No. Lo que es seguro y lo que no depende depende de su aplicación. Piense en una clase, que tiene los campos 'a' y' b' y la invariante, que exactamente uno de esos campos no es 'Ninguno' y el otro es' Ninguno'. A menos que el acceso esté correctamente enclavado, cualquier combinación aleatoria de 'a es [no] None' y' b es [no] None' puede observarse en clara violación del invariante, si solo se usa un getter/setter "ingenuo" (piense : 'def set_a (self, a): self.a = a; self.b = None si a no es None else self.b' - un hilo concurrente puede observar estados ilegales durante la ejecución) – Dirk

3

El GIL se encarga de eso, si usted está usando CPython.

bloqueo global intérprete

El bloqueo utilizado por hilos de Python para asegurar que sólo un hilo ejecuta en la máquina virtual CPython a la vez. Esto simplifica la implementación de CPython al garantizar que no haya dos procesos que puedan acceder a la misma memoria al mismo tiempo. Bloquear todo el intérprete hace que sea más fácil para el intérprete tener múltiples subprocesos, a expensas de gran parte del paralelismo que ofrecen las máquinas con múltiples procesadores. Se han realizado esfuerzos en el pasado para crear un intérprete de "hebra libre" (uno que bloquee los datos compartidos en una granularidad mucho más fina), pero hasta ahora ninguno ha sido exitoso porque el rendimiento sufrió en el caso de un solo procesador común.

Ver are-locks-unnecessary-in-multi-threaded-python-code-because-of-the-gil.

+0

Eso solo concierne a CPython. –

+0

A menos que esté usando Jython o IronPython. – voyager

+0

@Bastien Léonard: pásamelo :) – voyager

20

El,, manera portátil más segura posible para que cada obra hilo con datos independientes es:

import threading 
tloc = threading.local() 

Ahora cada hilo trabaja con un totalmente objeto independiente tloc aunque es un nombre global. El hilo puede obtener y establecer atributos en tloc, use tloc.__dict__ si necesita un diccionario específicamente, etc.

El almacenamiento local de subprocesos para un hilo desaparece al final del hilo; para que los subprocesos graben sus resultados finales, obtenga put sus resultados, antes de que finalicen, en una instancia común de Queue.Queue (que es intrínsecamente segura para subprocesos). De forma similar, los valores iniciales para los datos en los que un subproceso debe trabajarse podrían ser argumentos pasados ​​cuando se inicia el subproceso o tomados desde un Queue.

Otros enfoques a medias, como la esperanza de que las operaciones que parecen atómicas son atómicas, pueden funcionar para casos específicos en una versión dada y lanzamiento de Python, pero podrían romperse fácilmente por actualizaciones o puertos. No existe una razón real para arriesgar tales problemas cuando una arquitectura adecuada, limpia y segura es tan fácil de organizar, portátil, manejable y rápida.

11

Como necesitaba algo similar, aterricé aquí. Resumo sus respuestas en este breve fragmento:

#!/usr/bin/env python3 

import threading 

class ThreadSafeDict(dict) : 
    def __init__(self, * p_arg, ** n_arg) : 
     dict.__init__(self, * p_arg, ** n_arg) 
     self._lock = threading.Lock() 

    def __enter__(self) : 
     self._lock.acquire() 
     return self 

    def __exit__(self, type, value, traceback) : 
     self._lock.release() 

if __name__ == '__main__' : 

    u = ThreadSafeDict() 
    with u as m : 
     m[1] = 'foo' 
    print(u) 

como tal, puede utilizar el constructo with para mantener el bloqueo mientras juega en su dict()

1

Cómo funciona ?:

>>> import dis 
>>> demo = {} 
>>> def set_dict(): 
...  demo['name'] = 'Jatin Kumar' 
... 
>>> dis.dis(set_dict) 
    2   0 LOAD_CONST    1 ('Jatin Kumar') 
       3 LOAD_GLOBAL    0 (demo) 
       6 LOAD_CONST    2 ('name') 
       9 STORE_SUBSCR 
      10 LOAD_CONST    0 (None) 
      13 RETURN_VALUE 

Cada una de las instrucciones anteriores se ejecuta con retención de bloqueo GIL y la instrucción STORE_SUBSCR agrega/actualiza el par clave + valor en un diccionario. Entonces, ves que la actualización del diccionario es atómica y, por lo tanto, es segura para los hilos.

Cuestiones relacionadas