web-dev-qa-db-fra.com

Fatal Python et `BufferedWriter`

Je suis tombé sur ce paragraphe dans les documentations qui dit:

Les objets binaires tamponnés (instances de BufferedReader, BufferedWriter, BufferedRandom et BufferedRWPair) protègent leurs structures internes à l'aide d'un verrou; il est donc sûr de les appeler à partir de plusieurs threads à la fois.

Je ne sais pas pourquoi ils ont besoin de "protéger" leurs structures internes étant donné que le GIL est en action. On s'en fout? Je m'en fichais beaucoup jusqu'à ce que je découvre que ce verrou a une certaine signification, considérez ce morceau de code:

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")

Sortie lors de l'exécution sur 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

Sous Python 2.7, aucune erreur. Je ne sais pas pourquoi cela se produirait, cependant, j'ai regardé autour de bufferedio.c . Un autre code qui se comporte de manière similaire à l'extrait ci-dessus qui a été testé sur Python 3.X, parfois j'ai Fatal Python error et parfois non. Toute fonction filetée avec une boucle plus std[out][err].write provoque cette erreur fatale. Il est vraiment difficile de définir les caractéristiques de cette erreur et, à ma connaissance, la documentation ne mentionne rien à ce sujet. Je ne sais pas même si c'est un bug, j'espère que non.

Mon explication de ce comportement se présente ainsi: * Je peux me tromper totalement: le thread principal est sorti tout en maintenant le verrou de sys.stdout.buffer. Cependant, cela semble contraire au fait que les threads se terminent lorsque le thread principal se termine sur le système sur lequel j'exécute Python, Linux.


Je poste ceci comme réponse, cela ne peut tout simplement pas être fait dans la section des commentaires.

Ce comportement ne se limite pas à write il affecte read ainsi que flush appels sur ces objets BufferedReader, BufferedWriter, BufferedRandom et BufferedRWPair.

1) Sous Linux et probablement sous Windows également, lorsque le thread principal se termine, ses threads enfants sont arrêtés. Comment cela affecte-t-il le comportement mentionné en question? Si le thread principal a pu quitter pendant sa tranche de temps, avant d'être changé de contexte avec un autre thread, aucune erreur fatale ne se produit car tous les threads se terminent. Rien ne garantit cependant que le thread principal se terminera dès qu'il démarrera.

2) L'erreur fatale se produit entre le processus de finalisation (arrêt) de l'interpréteur et l'appel read, write ou flush et éventuellement d'autres opérations sur Buffered*. Le processus de finalisation acquiert le verrou de ces objets, tout write par exemple sur l'objet BufferedWriter donne Fatal Python error.

os._exit Termine l'interpréteur sans les étapes de finalisation et donc l'interpréteur ne sera pas propriétaire du verrou de l'objet dont nous parlons, voici un autre exemple:

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)

Dans le code ci-dessus, si le thread principal se termine dès qu'il démarre, c'est tout, aucune erreur fatale ne se produit et tous les threads générés sont arrêtés immédiatement (au moins sous Linux), cela dépend de la plate-forme. Si vous n'avez pas de chance et qu'un autre thread commence à jouer sur le terrain avant la fin des threads principaux, sans appel à os._exit(0), l'interpréteur passe par son cycle normal de finalisation pour acquérir le verrou de sys.stdout.buffer ce qui entraîne une erreur fatale. Exécutez ce code plusieurs fois pour remarquer ses différents comportements.

25
direprobs

TL; DR

Votre problème n'est pas strictement lié au verrouillage, mais avec le fait que vous essayez d'écrire dans un stdout qui n'existe plus avec un daemon thread.

Un peu d'expliquer

Lorsque vous exécutez votre script principal, l'interpréteur Python démarre et exécute votre code en ouvrant le descripteur de fichier stdout.

Lorsque votre script se termine sans attendre la fin des threads:

  • tous les threads passent de non-daemons à daemons
  • l'interpréteur quitte l'appel d'une fonction finalize qui efface les globaux des threads, y compris le stdout
  • les threads now-daemon essaient d'acquérir un verrou pour stdout qui n'est plus accessible en raison de l'étape précédente

Pour éviter ce problème, vous pouvez écrire dans un fichier au lieu de stdout (comme un thread démon devrait le faire) ou simplement attendre que les threads se terminent par quelque chose comme:

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")
13
mrnfrancesco

Quand j'ai exécuté le premier code sur windows (cygwin), j'ai eu l'erreur sur python3, mais j'ai aussi eu une erreur sur python2

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

Il est donc possible que sur votre plate-forme, python2.x ait quitté silencieusement les threads lorsqu'ils ne parviennent pas à obtenir le verrou. Je crois également que _ module de thread (thread en 2.7) est un module de bas niveau et ne garantit pas d'éviter ce comportement. Depuis aide du module

  • Lorsque le thread principal se termine, il est défini par le système si les autres threads survivent. Sur la plupart des systèmes, ils sont tués sans exécuter les clauses try ... finally ni exécuter les destructeurs d'objets.
  • Lorsque le thread principal se termine, il n'effectue aucun de ses nettoyages habituels (sauf que les clauses try ... finally sont honorées) et les fichiers d'E/S standard ne sont pas vidés.

Peut-être devriez-vous utiliser un niveau supérieur module de threading avec une synchronisation appropriée entre le thread principal et les autres threads.

5
Ketan Mukadam

Je pense que vous avez juste une compréhension erronée de GIL.

s'il vous plaît pensez à quand vous avez GIL et une liste, puis manipulez la liste dans différents threads, que se passera-t-il? si vous confondez toujours , testez-le. BufferedWriter aussi.

1
obgnaw