print
in Python n'est pas thread-safe selon thesearticles .
Une solution de contournement Python 3 est proposée dans ce dernier article.
Comment puis-je obtenir un thread sécurisé print
dans Python 2.6?
Problème intéressant - compte tenu de tout ce qui se passe dans une instruction print
, y compris la définition et la vérification de l'attribut softspace
, ce qui en fait "threadsafe" (ce qui signifie en fait: un thread qui imprime uniquement cède le "contrôle de la sortie standard" à un autre thread lorsqu'il imprime une nouvelle ligne, de sorte que chaque ligne entière qui sort est garantie de provenir d'un seul thread) était un peu difficile (l'approche facile habituelle de réelle) sécurité des threads - déléguer un thread séparé à "posséder" et gérer exclusivement sys.stdout
, communiquez avec elle via Queue.Queue - ce n'est pas très utile, car le problème est pas la sécurité des threads [[même avec un simple print
il n'y a aucun risque de planter et les caractères qui se retrouvent sur la sortie standard sont exactement ceux qui s'impriment]] mais le besoin d'exclusion mutuelle entre les threads pour une gamme étendue d'opérations).
Donc, je pense que je l'ai fait ...:
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'
Les appels à wait
sont destinés à garantir une sortie chaotique mélangée en l'absence de cette garantie d'exclusion mutuelle (d'où le commentaire). Avec le wrapping, c'est-à-dire le code ci-dessus exactement tel qu'il y apparaît, et (au moins) Python 2.5 et (je pense que cela peut aussi fonctionner dans les versions antérieures, mais je n'ai pas facilement à vérifier) la sortie est:
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...
L'effet de "sérialisation" (où les threads semblent "bien tournés" comme ci-dessus) est un effet secondaire du fait que le thread qui arrive à être en cours d'impression est sérieusement plus lent que les autres (toutes ces attentes! -). Commentant le time.sleep
dans wait
, la sortie est à la place
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...
c'est-à-dire une "sortie multithread" plus typique ... sauf pour la garantie que chaque ligne de la sortie provient entièrement d'un seul thread.
Bien sûr, un thread qui fait, par exemple, print 'ciao',
gardera la "propriété" de la sortie standard jusqu'à ce qu'elle effectue finalement une impression sans virgule de fin, et les autres threads souhaitant imprimer peuvent rester en veille pendant assez longtemps un moment (comment peut-on garantir autrement que chaque ligne dans la sortie provient d'un seul thread? eh bien, une architecture serait d'accumuler des lignes partielles pour stocker le stockage local au lieu de les écrire réellement sur la sortie standard, et de ne faire que l'écriture à la réception du \n
... délicat à entrelacer correctement avec softspace
paramètres, je le crains, mais probablement faisable).
Le problème est que python utilise des opcodes séparés pour l'impression NEWLINE et l'impression de l'objet lui-même. La solution la plus simple est probablement d'utiliser simplement un sys.stdout.write explicite avec une nouvelle ligne explicite.
Grâce à l'expérimentation, j'ai trouvé que les travaux suivants sont simples et répondent à mes besoins:
print "your string here\n",
Ou, enveloppé dans une fonction,
def safe_print(content):
print "{0}\n".format(content),
Ma compréhension est que la nouvelle ligne implicite d'un print
normal est en fait sortie vers stdout dans une opération distincte, provoquant la condition de concurrence avec d'autres opérations print
. En supprimant cette nouvelle ligne implicite avec le ,
, et au lieu d'inclure la nouvelle ligne dans la chaîne, nous pouvons éviter ce problème.
2017 Edit: cette réponse commence à prendre de la vapeur, donc je voulais juste apporter une clarification. Cela ne rend pas réellement print
"thread-safe" exactement. La sortie peut être dans le mauvais ordre si les print
se produisent en microsecondes les unes des autres. Ce que cela fait , cependant, est d'éviter la sortie tronquée provenant des instructions print
exécutées à partir de threads simultanés, ce que la plupart des gens veulent vraiment en posant cette question.
Voici un test pour montrer ce que je veux dire:
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)
Production:
Normal Print:
0
1
23
4
65
7
9
8
----
Safe Print:
1
0
3
2
4
5
6
7
8
9
Je ne sais pas s'il y a une meilleure façon à la place de ce mécanisme de verrouillage, mais au moins ça a l'air facile. Je ne sais pas non plus si l'impression n'est pas vraiment sûre pour les threads.
Edit: OK, je l'ai testé moi-même maintenant, vous avez raison, vous pouvez obtenir une sortie vraiment bizarre. Et vous n'avez pas besoin de l'import future, c'est juste là, car j'utilise 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')