threads threading thread example ejemplos current_thread basics python linux multithreading python-2.7 python-internals

threading - Error fatal de Python y `BufferedWriter`



threading python ejemplos (3)

Me he encontrado con este párrafo en las Documentaciones que dice:

Los objetos búferes almacenados (instancias de BufferedReader , BufferedWriter , BufferedRandom y BufferedRWPair ) protegen sus estructuras internas mediante un bloqueo; por lo tanto, es seguro llamarlos desde múltiples hilos a la vez.

No estoy seguro de por qué necesitan "proteger" sus estructuras internas dado que GIL está en acción. ¿A quien le importa? No me importó mucho hasta que descubrí que este bloqueo tiene cierta importancia, considera este fragmento de código:

from _thread import start_new_thread import time def start(): for i in range(10): print("SPAM SPAM SPAM!") for i in range(10): start_new_thread(start, ()) time.sleep(0.0001) print("main thread exited")

Salida cuando se ejecuta en Python 3.X:

...many SPAM... SPAM SPAM SPAM! SPAM SPAM SPAM! SPAM SPAM SPAM! main thread exited SPAM SPAM SPAM! SPAM SPAM SPAM! SPAM SPAM SPAM! SPAM SPAM SPAM! SPAM SPAM SPAM! Fatal Python error: could not acquire lock for <_io.BufferedWritername=''<stdout>''> at interpreter shutdown, possibly due to daemon threads

Bajo Python 2.7, no hay errores. No sé por qué sucedería esto, sin embargo, he estado buscando en bufferedio.c . Otro código que se comporta de manera similar al fragmento anterior que se probó en Python 3.X, a veces recibía el Fatal Python error y otras veces no. Cualquier función de subproceso con un bucle más std[out][err].write causa este error fatal. Es realmente difícil definir las características de este error y, según mi conocimiento, la Documentación no menciona nada al respecto. No estoy seguro de si es un error, espero que no.

Mi explicación de este comportamiento es la siguiente: * Podría estar totalmente equivocado: el hilo principal salió mientras mantenía el bloqueo de sys.stdout.buffer . Sin embargo, esto parece contrario al hecho de que los subprocesos terminan cuando el subproceso principal se cierra en el sistema en el que ejecuto Python, Linux.

Estoy publicando esto como respuesta, simplemente no se puede hacer en la sección de comentarios.

Este comportamiento no solo se limita a write sino que afecta tanto a la read como a las llamadas flush en esos objetos BufferedReader , BufferedWriter , BufferedRandom y BufferedRWPair .

1) En Linux y probablemente también en Windows, cuando el hilo principal se cierra, sus hilos secundarios terminan. ¿Cómo afecta esto el comportamiento mencionado en cuestión? Si el subproceso principal pudo salir durante su segmento de tiempo, antes de cambiar de contexto con otro subproceso, no se produce ningún error fatal cuando se terminan todos los subprocesos. Sin embargo, nada garantiza que el hilo principal se cerrará tan pronto como comience.

2) El error fatal tiene lugar entre el proceso de finalización (apagado) del intérprete y la llamada de read , write o flush y posiblemente otras operaciones en el objeto Buffered* . El proceso de finalización adquiere el bloqueo de esos objetos, cualquier write por ejemplo, en el objeto BufferedWriter da como resultado un Fatal Python error .

os._exit termina el intérprete sin los pasos de finalización y, por lo tanto, el intérprete no será propietario del bloqueo del objeto del que estamos hablando, este es otro ejemplo:

from _thread import start_new_thread import time, sys, os def start(myId): for i in range(10): sys.stdout.buffer.write(b"SPAM/n") for i in range(2): start_new_thread(start, (i,)) x = print("main thread") print(x) #os._exit(0)

En el código anterior, si el subproceso principal sale tan pronto como se inicia, eso es todo, no se produce ningún error grave y todos los subprocesos generados se terminan inmediatamente (al menos en Linux), aunque esto depende de la plataforma. Si tiene la mala suerte y otro hilo comenzó a reproducirse en el campo antes de que salga el hilo principal, sin os._exit(0) el intérprete pasa por su ciclo normal de finalización para adquirir el bloqueo de sys.stdout.buffer que resulta en error fatal. Ejecuta este código varias veces para notar sus diferentes comportamientos.


TL; DR

Su problema no está estrictamente relacionado con el bloqueo, pero con el hecho de que está intentando escribir en una salida stdout existe con un daemon thread .

Un poco de explicacion

Cuando ejecuta su script principal, el intérprete de Python se inicia y ejecuta su código abriendo el descriptor de archivo stdout .

Cuando su script termina sin esperar a que terminen los hilos:

  • Todos los hilos pasan de no demonios a demonios.
  • el intérprete sale llamando a una función de finalización que borra las variables globales de los hilos, incluida la salida estándar
  • los subprocesos del ahora daemon intentan adquirir un bloqueo para la stdout que no es más accesible debido al paso anterior

Para evitar este problema, puede escribir en el archivo en lugar de en la salida estándar (como debería hacerlo un subproceso del daemon) o simplemente esperar a que los subprocesos terminen con algo como:

from threading import Thread import time def start(): for i in range(10): print("SPAM SPAM SPAM!") # create a thread list (you''ll need it later) threads = [Thread(target=start, args=()) for i in range(10)] # start all the threads for t in threads: t.start() # or [t.start() for t in threads] if you prefer the inlines time.sleep(0.0001) # wait for threads to finish for t in threads: t.join() # or [t.join() for t in threads] for the inline version print("main thread exited")


Creo que tienes un entendimiento erróneo de GIL.

por favor, piense cuando tenga GIL y una lista, luego manipule la lista en diferentes hilos, ¿qué pasará? si todavía confunde, pruébelo. También lo hace el BufferedWriter .


Cuando ejecuté el primer código en windows (cygwin), obtuve el error en python3, pero también obtuve un error en python2

> Unhandled exception in thread started by > sys.excepthook is missing > lost sys.stderr

Por lo tanto, es posible que en su plataforma python2.x haya salido silenciosamente de los subprocesos cuando no logran adquirir el bloqueo. También creo que el módulo _thread (hilo en 2.7) es un módulo de bajo nivel y no garantiza evitar este comportamiento. Desde el módulo de ayuda.

  • Cuando el hilo principal sale, el sistema define si los otros subprocesos sobreviven. En la mayoría de los sistemas, se eliminan sin ejecutar cláusulas try ... finalmente o ejecutando destructores de objetos.
  • Cuando el subproceso principal se cierra, no realiza ninguna de las tareas de limpieza habituales (excepto que se respetan las cláusulas de prueba ... finalmente), y los archivos de E / S estándar no se eliminan.

Es posible que deba utilizar un módulo de subprocesos de nivel superior con la sincronización adecuada entre los subprocesos principales y otros.