Noto que a menudo se sugiere usar colas con múltiples hilos, en lugar de listas y .pop()
. ¿Es esto porque las listas no son seguras para subprocesos o por alguna otra razón?
Noto que a menudo se sugiere usar colas con múltiples hilos, en lugar de listas y .pop()
. ¿Es esto porque las listas no son seguras para subprocesos o por alguna otra razón?
Respuestas:
Las listas en sí son seguras para subprocesos. En CPython, el GIL protege contra accesos concurrentes a ellos, y otras implementaciones se encargan de usar un bloqueo de grano fino o un tipo de datos sincronizado para sus implementaciones de listas. Sin embargo, si bien las listas en sí no pueden corromperse por intentos de acceso simultáneo, los datos de las listas no están protegidos. Por ejemplo:
L[0] += 1
no se garantiza que aumente L [0] en uno si otro hilo hace lo mismo, porque +=
no es una operación atómica. (Muy, muy pocas operaciones en Python son realmente atómicas, porque la mayoría de ellas pueden provocar que se invoque un código arbitrario de Python). Debería usar Colas porque si solo usa una lista desprotegida, puede obtener o eliminar el elemento incorrecto debido a la raza condiciones
Para aclarar un punto en la excelente respuesta de Thomas, debe mencionarse que append()
es seguro para subprocesos.
Esto se debe a que no hay preocupación de que los datos que se leen estarán en el mismo lugar una vez que lo escribamos . La append()
operación no lee datos, solo escribe datos en la lista.
PyList_Append
se realiza en un bloqueo GIL. Se le da una referencia a un objeto para agregar. El contenido de ese objeto puede cambiarse después de que se evalúa y antes de que PyList_Append
se realice la llamada . Pero seguirá siendo el mismo objeto y se agregará de forma segura (si lo hace lst.append(x); ok = lst[-1] is x
, ok
puede ser falso, por supuesto). El código al que hace referencia no lee del objeto adjunto, excepto para INCREMENTARLO. Lee y puede reasignar la lista a la que se agrega.
L[0] += x
realizará un __getitem__
encendido L
y luego un __setitem__
encendido L
; si es L
compatible __iadd__
, hará las cosas un poco diferente en la interfaz del objeto, pero todavía hay dos operaciones separadas L
en el nivel de intérprete de Python (las verá en el bytecode compilado). El append
se realiza en una llamada de método único en el código de bytes.
remove
?
Aquí hay una lista completa pero no exhaustiva de ejemplos de list
operaciones y si son seguros o no para subprocesos. Con la esperanza de obtener una respuesta con respecto a la obj in a_list
construcción del lenguaje aquí .
Recientemente tuve este caso en el que necesitaba agregar una lista continuamente en un hilo, recorrer los elementos y verificar si el elemento estaba listo, en mi caso era un AsyncResult y eliminarlo de la lista solo si estaba listo. No pude encontrar ningún ejemplo que demostrara mi problema claramente. Aquí hay un ejemplo que demuestra agregar a la lista en un hilo continuamente y eliminar de la misma lista en otro hilo continuamente. unas pocas veces y verás el error
La versión defectuosa
import threading
import time
# Change this number as you please, bigger numbers will get the error quickly
count = 1000
l = []
def add():
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
Salida cuando ERROR
Exception in thread Thread-63:
Traceback (most recent call last):
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 916, in _bootstrap_inner
self.run()
File "/Users/zup/.pyenv/versions/3.6.8/lib/python3.6/threading.py", line 864, in run
self._target(*self._args, **self._kwargs)
File "<ipython-input-30-ecfbac1c776f>", line 13, in remove
l.remove(i)
ValueError: list.remove(x): x not in list
Versión que usa cerraduras
import threading
import time
count = 1000
l = []
lock = threading.RLock()
def add():
with lock:
for i in range(count):
l.append(i)
time.sleep(0.0001)
def remove():
with lock:
for i in range(count):
l.remove(i)
time.sleep(0.0001)
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=remove)
t1.start()
t2.start()
t1.join()
t2.join()
print(l)
Salida
[] # Empty list
Conclusión
Como se mencionó en las respuestas anteriores, mientras que el acto de agregar o extraer elementos de la lista es seguro para subprocesos, lo que no es seguro para subprocesos es cuando se agrega en un subproceso y aparece en otro
with r:
) en lugar de llamar explícitamente r.acquire()
yr.release()