web-dev-qa-db-ja.com

Fatal Python error and `BufferedWriter`

ドキュメンテーションでこの段落に出くわしました:

バイナリバッファオブジェクト(BufferedReaderBufferedWriterBufferedRandomBufferedRWPairのインスタンス)は、ロックを使用して内部構造を保護します。したがって、一度に複数のスレッドから呼び出すのが安全です。

GILが機能しているのに、なぜ内部構造を「保護」する必要があるのか​​はわかりません。誰も気にしない?このロックがいくつかの重要性を持っていることがわかるまで、私はあまり気にしていませんでした。

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

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

Python 2.7、エラーなし。なぜこれが起こるのかわかりませんが、私は bufferedio.c で周りを見回しています。動作する別のコードPython 3.Xでテストされた上記のスニペットと同様に、Fatal Python errorが得られる場合と得られない場合があります。ループとstd[out][err].writeを含むスレッド関数は、この致命的なエラーを引き起こします。このエラーの特性を定義し、私の知る限り、ドキュメントには何も記載されていません。バグであるかどうかはわかりませんが、そうではないと思います。

この動作についての私の説明は次のようになります。*完全に間違っている可能性があります。メインのスレッドがsys.stdout.bufferのロックを保持している間に終了しました。ただし、これは、Python、Linuxを実行しているシステムでメインスレッドが終了するとスレッドが終了するという事実とは対照的です。


私はこれを答えとして投稿していますが、コメントセクションではできません。

この動作はwriteだけに限定されているわけではなく、readだけでなく、これらのオブジェクトのflush呼び出しにも影響しますBufferedReaderBufferedWriterBufferedRandomおよびBufferedRWPair

1)LinuxおよびおそらくWindowsでも、メインスレッドが終了すると、その子スレッドが終了します。これは、問題の上記の動作にどのように影響しますか?メインスレッドがタイムスライス中に終了できた場合、別のスレッドとコンテキストが切り替えられる前に、すべてのスレッドが終了しても致命的なエラーは発生しません。ただし、メインスレッドが開始するとすぐに終了することを保証するものはありません。

2)インタープリターのファイナライズプロセス(シャットダウン)とreadwrite、またはflush呼び出し、およびBuffered*オブジェクトの他の操作の間で致命的なエラーが発生します。ファイナライズプロセスはこれらのオブジェクトのロックを取得します。たとえば、writeオブジェクトへのBufferedWriterFatal Python errorになります。

os._exitは、ファイナライズ手順なしでインタープリターを終了します。したがって、インタープリターは、話しているオブジェクトのロックを所有しません。これは別の例です。

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)

上記のコードで、メインスレッドが開始するとすぐに終了する場合、それだけです。致命的なエラーは発生せず、生成されたすべてのスレッドは直ちに終了します(少なくともLinuxでは)これはプラットフォームに依存します。運が悪すぎて、メインスレッドが終了する前に別のスレッドがフィールドで再生を開始した場合、os._exit(0)呼び出しなしでインタープリターは通常のファイナライズサイクルを実行してsys.stdout.bufferのロックを取得し、致命的なエラーが発生します。 。このコードを複数回実行して、さまざまな動作を確認します。

25
direprobs

TL; DR

あなたの問題は厳密にロックに関するものではありませんが、存在しないstdoutdaemon thread

少し説明する

メインスクリプトを実行すると、Pythonインタープリターが起動し、stdoutファイル記述子を開いてコードを実行します。

スレッドの終了を待たずにスクリプトが終了すると、次のようになります。

  • すべてのスレッドが非デーモンからデーモンに切り替わります
  • インタプリタは、stdoutを含むスレッドのグローバルをワイプするfinalize関数の呼び出しを終了します。
  • 現在のデーモンスレッドは、前の手順のためにアクセスできなくなったstdoutのロックを取得しようとします

この問題を回避するには、(デーモンスレッドが行うように)stdoutの代わりにファイルに書き込むか、スレッドが次のようなもので終了するのを待つだけです。

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

Windows(cygwin)で最初のコードを実行すると、python3でエラーが発生しましたが、python2でもエラーが発生しました

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

そのため、ご使用のプラットフォームでは、python2.xがロックの取得に失敗したときに、暗黙的にスレッドを終了した可能性があります。また、私は _ thread module (2.7のスレッド)は低レベルのモジュールであり、この動作を回避することを保証しないと信じています。 モジュールヘルプ から

  • メインスレッドが終了すると、他のスレッドが存続するかどうかはシステムによって定義されます。ほとんどのシステムでは、try ... finally節を実行したり、オブジェクトデストラクタを実行したりせずに強制終了されます。
  • メインスレッドが終了すると、通常のクリーンアップは行われず(try ... finally節が順守されている場合を除く)、標準のI/Oファイルはフラッシュされません。

より高いレベルの threading module をメインスレッドと他のスレッドの間で適切に同期させて使用する必要があるかもしれません。

5
Ketan Mukadam

GILを誤って理解しているだけだと思います。

gILとリストがある場合を考えてください。その後、別のスレッドでリストを操作してください。それでも混乱する場合は、テストしてください。BufferedWriterもテストします。

1
obgnaw