¿Cómo obtengo una impresión segura para subprocesos en Python 2.6?

print en Python no es segura para subprocesos según estos artículos

En este último artículo se ofrece una solución de Python 3.

¿Cómo obtengo una print segura de hilos en Python 2.6?

Problema interesante: teniendo en cuenta todas las cosas que suceden dentro de una statement de print , incluida la configuración y la comprobación del atributo de softspace , lo que lo hace “seguro para subprocesos” (es decir, un subproceso que se está imprimiendo solo proporciona “control de salida estándar” a otro subproceso cuando se imprime una nueva línea, por lo que se garantiza que cada línea completa que se emita provendrá de un solo hilo) fue un poco difícil (el enfoque fácil y habitual de la seguridad real del hilo: delegar un hilo por separado en “propietario” y manejo exclusivo sys.stdout , sys.stdout a través de Queue.Queue: no es tan útil, ya que el problema no es la seguridad de subprocesos [[incluso con una print simple no existe riesgo de fallo y los caracteres que terminan en la salida estándar son exactamente aquellos que se imprimen]], pero la necesidad de exclusión mutua entre hilos para un rango extendido de operaciones).

Entonces, creo que lo hice …

 import random import sys import thread import threading import time def wait(): time.sleep(random.random()) return 'W' def targ(): for n in range(8): wait() print 'Thr', wait(), thread.get_ident(), wait(), 'at', wait(), n tls = threading.local() class ThreadSafeFile(object): def __init__(self, f): self.f = f self.lock = threading.RLock() self.nesting = 0 def _getlock(self): self.lock.acquire() self.nesting += 1 def _droplock(self): nesting = self.nesting self.nesting = 0 for i in range(nesting): self.lock.release() def __getattr__(self, name): if name == 'softspace': return tls.softspace else: raise AttributeError(name) def __setattr__(self, name, value): if name == 'softspace': tls.softspace = value else: return object.__setattr__(self, name, value) def write(self, data): self._getlock() self.f.write(data) if data == '\n': self._droplock() # comment the following statement out to get guaranteed chaos;-) sys.stdout = ThreadSafeFile(sys.stdout) thrs = [] for i in range(8): thrs.append(threading.Thread(target=targ)) print 'Starting' for t in thrs: t.start() for t in thrs: t.join() print 'Done' 

Las llamadas a wait tienen la intención de garantizar un resultado mixto caótico en ausencia de esta garantía de exclusión mutua (de ahí el comentario). Con el envoltorio, es decir, el código anterior exactamente como se ve allí, y (al menos) Python 2.5 y superior (creo que esto también puede ejecutarse en versiones anteriores, pero no tengo ninguna a la mano para verificar) la salida es:

 Thr W -1340583936 W at W 0 Thr W -1340051456 W at W 0 Thr W -1338986496 W at W 0 Thr W -1341116416 W at W 0 Thr W -1337921536 W at W 0 Thr W -1341648896 W at W 0 Thr W -1338454016 W at W 0 Thr W -1339518976 W at W 0 Thr W -1340583936 W at W 1 Thr W -1340051456 W at W 1 Thr W -1338986496 W at W 1 ...more of the same... 

El efecto de “serialización” (por el cual los hilos parecen ser “bien redondos” como es arriba) es un efecto secundario del hecho de que el hilo que llega a ser el que se está imprimiendo es seriamente más lento que los otros (¡todas esas esperas! -). Comentando el time.sleep en wait , la salida es en cambio

 Thr W -1341648896 W at W 0 Thr W -1341116416 W at W 0 Thr W -1341648896 W at W 1 Thr W -1340583936 W at W 0 Thr W -1340051456 W at W 0 Thr W -1341116416 W at W 1 Thr W -1341116416 W at W 2 Thr W -1338986496 W at W 0 ...more of the same... 

es decir, una “salida multiproceso” más típica … excepto por la garantía de que cada línea en la salida proviene completamente de un solo hilo.

Por supuesto, un subproceso que, por ejemplo, print 'ciao', mantendrá la “propiedad” de la salida estándar hasta que finalmente realice una impresión sin una coma al final, y otros subprocesos que deseen imprimir pueden quedarse inactivos durante bastante tiempo (cómo ¿Se puede garantizar que cada línea en la salida proviene de un solo subproceso? Bueno, una architecture sería acumular líneas parciales para enhebrar el almacenamiento local en lugar de escribirlas en la salida estándar, y solo hacer la escritura al recibir el \n . .. delicado para intercalar correctamente con la configuración de softspace software, me temo, pero probablemente factible).

El problema es que Python utiliza códigos de operación separados para la impresión NEWLINE y la impresión del objeto en sí. La solución más fácil es, probablemente, usar un sys.stdout.write explícito con una nueva línea explícita.

A través de la experimentación, descubrí que los siguientes trabajos son simples y se adaptan a mis necesidades:

 print "your string here\n", 

O, envuelto en una función,

 def safe_print(content): print "{0}\n".format(content), 

Tengo entendido que la nueva línea implícita de una print normal en realidad se envía a la salida estándar en una operación separada, lo que causa la condición de carrera con otras operaciones de print . Al eliminar esta nueva línea implícita con la adición , y en lugar de incluir la nueva línea en la cadena, podemos evitar este problema.


Edición de 2017: esta respuesta está empezando a cobrar fuerza, así que solo quería hacer una aclaración. Esto en realidad no hace que la print “segura para subprocesos” exactamente. La salida puede estar en el orden incorrecto si las print producen con microsegundos de diferencia entre sí. Sin embargo, lo que esto hace es evitar la salida confusa que proviene de print declaraciones de print ejecutadas desde subprocesos concurrentes, que es lo que la mayoría de la gente realmente quiere cuando se hace esta pregunta.

Aquí hay una prueba para mostrar lo que quiero decir:

 from concurrent.futures import ThreadPoolExecutor def normal_print(content): print content def safe_print(content): print "{0}\n".format(content), with ThreadPoolExecutor(max_workers=10) as executor: print "Normal Print:" for i in range(10): executor.submit(normal_print, i) print "---" with ThreadPoolExecutor(max_workers=10) as executor: print "Safe Print:" for i in range(10): executor.submit(safe_print, i) 

Salida:

 Normal Print: 0 1 23 4 65 7 9 8 ---- Safe Print: 1 0 3 2 4 5 6 7 8 9 

No sé si hay alguna forma mejor de este mecanismo de locking, pero al menos parece fácil. Tampoco estoy seguro de si la impresión realmente no es segura para subprocesos.

Edit: está bien, lo he probado yo mismo, tienes razón, puedes obtener resultados realmente extraños. Y no necesita la importación futura , simplemente está ahí, porque uso Python 2.7.

 from __future__ import print_function from threading import Lock print_lock = Lock() def save_print(*args, **kwargs): with print_lock: print (*args, **kwargs) save_print("test", "omg", sep='lol')