致命的Python错误和'BufferedWriter`

时间:2017-07-23 16:35:36

标签: python linux multithreading python-2.7 python-internals

我在文件中发现了这段话:

  

二进制缓冲对象(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对这些对象BufferedReaderBufferedWriter,{{1}的调用}和BufferedRandom

1)在Linux上,也可能在Windows上,当主线程退出时,其子线程终止。这如何影响上述行为?如果主线程在其时间片期间能够退出,则在与另一个线程进行上下文切换之前,不会发生致命错误,因为所有线程都会终止。但是,没有什么可以保证主线程一旦启动就会退出。

2)致命错误发生在解释器的完成过程(关闭)与BufferedRWPairreadwrite调用以及{{1}上可能的其他操作之间对象。最终确定过程获取这些对象的锁定,例如flushBuffered*对象导致write

BufferedWriter在没有完成步骤的情况下终止解释器,因此解释器不会拥有我们正在谈论的对象的锁,这是另一个例子:

Fatal Python error

在上面的代码中,如果主线程一启动就退出,就是这样,不会发生致命错误,并且所有生成的线程都会立即终止(至少在Linux中)这是平台相关的。如果你不够运行并且在主线程退出之前另一个线程开始在该字段上播放,没有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) 的锁定,这将导致致命错误。多次运行此代码以注意其不同的行为。

3 个答案:

答案 0 :(得分:9)

TL; DR

您的问题与锁定内容并不严格相关,但事实上您尝试使用stdout写入不再存在的daemon thread

一点解释

运行主脚本时,Python解释器启动并执行打开stdout文件描述符的代码。

当你的脚本结束而不等待线程完成时:

  • 所有线程从非守护进程切换到守护进程
  • 解释器退出调用 finalize 函数,该函数会输入线程&#39;全局包括stdout
  • now-daemon线程尝试获取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")

答案 1 :(得分:5)

当我在windows(cygwin)上运行第一个代码时,我在python3上得到了错误,但我在python2上也出错了

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

因此,在您的平台上,python2.x可能在无法获取锁定时以静默方式退出线程。另外我认为_thread module(2.7中的线程)是一个低级模块,并不保证避免这种行为。来自module help

  
      
  • 当主线程退出时,系统定义其他线程是否存活。在大多数系统中,它们都会被杀死而不执行   尝试... finally子句或执行对象析构函数。
  •   
  • 当主线程退出时,它不执行任何常规清理(除了尝试...最终条款被尊重),以及标准   不刷新I / O文件。
  •   

可能是您应该使用更高级别threading module,并在主线程和其他线程之间进行适当的同步。

答案 2 :(得分:1)

我认为你对GIL有错误的理解。

请考虑一下当你有GIL和一个列表,然后在不同的线程中操作列表,会发生什么?如果你仍然混淆,测试它。那么BufferedWriter