Подтвердить что ты не робот

Неустранимая ошибка Python и `BufferedWriter`

Я нашел этот абзац в Документах, в которых говорится:

Бинарные буферизованные объекты (экземпляры BufferedReader, BufferedWriter, BufferedRandom и BufferedRWPair) защищают свои внутренние структуры с помощью блокировки; поэтому безопасно называть их сразу из нескольких потоков.

Я не уверен, почему они должны "защищать" свои внутренние структуры, учитывая, что 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 вызывает те объекты BufferedReader, BufferedWriter, BufferedRandom и BufferedRWPair.

1) В Linux и, возможно, в Windows тоже, когда основной поток завершается, его дочерние потоки прекращаются. Как это влияет на указанное поведение? Если основной поток смог выйти во время его среза времени, перед тем как переключиться на контекст с другим потоком, фатальная ошибка не возникает, поскольку все потоки прекращаются. Однако ничего не гарантирует, что основной поток выйдет, как только он начнется.

2) Фатальная ошибка происходит между процессом финализации (выключения) интерпретатора и вызовом read, write или flush и, возможно, другими операциями над объектом Buffered*. Процесс завершения получает блокировку этих объектов, любой write, например, для объекта BufferedWriter, приводит к Fatal 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, что приводит к фатальной ошибке. Запустите этот код несколько раз, чтобы заметить его разные поведения.

4b9b3361

Ответ 1

Когда я запускал первый код в 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 clauses соблюдены), а стандартный Файлы ввода-вывода не сбрасываются.

Возможно, вам следует использовать более высокий уровень модуль потоковой передачи с надлежащей синхронизацией между основным и другими потоками.

Ответ 2

TL; DR

Ваша проблема не связана строго с файлом блокировки, но с тем фактом, что вы пытаетесь написать более не существующий stdout с daemon thread.

Немного объяснения

При запуске основного script запускается интерпретатор Python и запускает ваш код, открывая дескриптор файла stdout.

Когда ваш script заканчивается, не дожидаясь завершения потоков:

  • все потоки переключаются с не-демонов на демонов
  • интерпретатор завершает вызов функции завершения, которая определяет глобальные потоки, включая 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")

Ответ 3

Я думаю, что у вас просто ошибочное понимание GIL.

подумайте о том, когда у вас есть GIL и список, а затем манипулируйте списком в разных потоках, что произойдет, если вы все еще запутаетесь, проверьте его. Кроме того, BufferedWriter.