- Оригинальный адрес:How I fixed a very old GIL race condition in Python 3.7
- Оригинальный автор:Victor Stinner
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:kezhenxu94
- Корректор:Starrier
На исправление серьезной ошибки в знаменитой библиотеке Python GIL (Global Interpreter Lock) у меня ушло 4 года., Python GIL — одна из самых подверженных ошибкам частей Python. Мне пришлось покопаться в истории коммитов Git, чтобы найти 26 лет назад.Guido van RossumПредставленная запись: В то время,Темы все еще очень неясная вещь. И слушай меня медленно.
Фатальная ошибка Python, вызванная потоком C и GIL
В марте 2014 г.Steve DowerСообщается об ошибке, когда «поток C» использует Python C API.bpo-20891:
В Python 3.4rc3 вызывается в потоке, не созданном в Python.
PyGILState_Ensure()
метод, но не вызываетPyEval_InitThreads()
метод, это приведет к сбою программы с фатальной ошибкой:
Fatal Python error: take_gil: NULL tstate
Мой первый комментарий:
по моему это
PyEval_InitThreads()
Жук.
Исправление PyGILState_Ensure()
Я забыл об этой ошибке в течение двух лет. К марту 2016 года я изменил тестовый код Стива, чтобы он был совместим с Linux (в то время тестовый код был написан для Windows). Я успешно воспроизвел эту ошибку на своем компьютере и написалPyGILState_Ensure()
патч для ремонта.
Через год, в ноябре 2017 г.Marcin Kasperskiспросил:
Это исправление уже выпущено? Я не вижу его в журнале изменений...
Ой, опять я совсем забыл про этот вопрос! На этот раз я не толькоОтправлено мое исправление для PyGILState_Ensure(), тоже писалмодульный тест test_embed.test_bpo20891()
:
Что ж, эта ошибка была исправлена в Python 2.7, 3.6 и основной ветке (позднее 3.7). В версии 3.6 и основной версии этот патч также поставляется с модульными тестами.
мое исправление фиксируется в основной ветке, фиксируетсяb4d1e1f7:
bpo-20891: Fix PyGILState_Ensure() (#4650)
When PyGILState_Ensure() is called in a non-Python thread before
PyEval_InitThreads(), only call PyEval_InitThreads() after calling
PyThreadState_New() to fix a crash.
Add an unit test in test_embed.
Тогда я закрыл эту темуbpo-20891в настоящее время...
Модульные тесты случайным образом аварийно завершают работу в macOS
Все было хорошо... пока через неделю я не понял, что мой недавно добавленный модульный тест был на macOS.временамирухнет. В конце концов мне удалось найти путь для воспроизведения, следующий пример вылетает при третьем запуске:
macbook:master haypo$ while true; do ./Programs/_testembed bpo20891 ||break; date; done
Lun 4 déc 2017 12:46:34 CET
Lun 4 déc 2017 12:46:34 CET
Lun 4 déc 2017 12:46:34 CET
Fatal Python error: PyEval_SaveThread: NULL tstate
Current thread 0x00007fffa5dff3c0 (most recent call first):
Abort trap: 6
test_embed.test_bpo20891()
на macOSPyGILState_Ensure()
Возникло состояние гонки: сам замок GIL сконструирован... никакой защиты замка! Очевидно, что добавление блокировки для определения того, есть ли в настоящее время в Python блокировка GIL, бессмысленно...
Я предлагаю исправитьPyThread_start_new_thread()
Не очень полное предложение:
Я нашел работоспособное исправление: в
PyThread_start_new_thread()
вызыватьPyEval_InitThreads()
. Таким образом, GIL может быть создан, как только будет создан второй поток. GIL больше не может быть создан при наличии двух запущенных потоков. Но, по крайней мере, в "Разве этоpython
«В этой черно-белой ситуации, если поток не был создан в Python, исправление не удастся, но тогда поток вызоветPyGILState_Ensure()
.
Почему бы не создать GIL в первую очередь?
Antoine PitrouЗадал простой вопрос:
почему нетКогда парсер инициализируетсяпросто позвони
PyEval_InitThreads()
? Есть ли недостаток?
благодаряgit blame
иgit log
команда, я нашел источник кода "создать GIL по запросу",Изменение по сравнению с 26-летней давностью!
commit 1984f1e1c6306d4e8073c28d2395638f80ea509b
Author: Guido van Rossum <guido@python.org>
Date: Tue Aug 4 12:41:02 1992 +0000
* Makefile adapted to changes below.
* split pythonmain.c in two: most stuff goes to pythonrun.c, in the library.
* new optional built-in threadmodule.c, build upon Sjoerd's thread.{c,h}.
* new module from Sjoerd: mmmodule.c (dynamically loaded).
* new module from Sjoerd: sv (svgen.py, svmodule.c.proto).
* new files thread.{c,h} (from Sjoerd).
* new xxmodule.c (example only).
* myselect.h: bzero -> memset
* select.c: bzero -> memset; removed global variable
(...)
+void
+init_save_thread()
+{
+#ifdef USE_THREAD
+ if (interpreter_lock)
+ fatal("2nd call to init_save_thread");
+ interpreter_lock = allocate_lock();
+ acquire_lock(interpreter_lock, 1);
+#endif
+}
+#endif
Я предполагаю, что цель этого динамического создания GIL состоит в том, чтобы избежать «преждевременного» создания GIL приложениями, которые используют только один поток (т. е. никогда не создают новый поток).
К счастью,Guido van RossumБыл там в то время и смог работать со мной, чтобы выяснить основную причину:
Да, первоначальная причина былаПотоки очень неясны, и не так много кода, который использует потоки., то из-за багов в коде GIL мы бы точно почувствовалиЧастое использование GIL приводит к (незначительному) снижению производительности.иРастущий риск аварии. Теперь, когда мы знаем, что нам больше не нужно беспокоиться об этих двух аспектах, мы можемНе стесняйтесь инициализировать его с помощью.
Предлагаемое второе исправление для Py_Initialize()
я предложилPy_Initialize()
изЕще одно исправление: Всегда создавайте GIL сразу после запуска Python, а не «по запросу», чтобы избежать риска возникновения условий гонки:
+ /* Create the GIL */
+ PyEval_InitThreads();
Nick CoghlanСпросите меня, могу ли я запустить тест производительности на моем патче. я в своемPR 4700запускать наpyperformance, разница до 5%:
haypo@speed-python$ python3 -m perf compare_to \
2017-12-18_12-29-master-bd6ec4d79e85.json.gz \
2017-12-18_12-29-master-bd6ec4d79e85-patch-4700.json.gz \
--table --min-speed=5
+----------------------+--------------------------------------+-------------------------------------------------+
| Benchmark | 2017-12-18_12-29-master-bd6ec4d79e85 | 2017-12-18_12-29-master-bd6ec4d79e85-patch-4700 |
+======================+======================================+=================================================+
| pathlib | 41.8 ms | 44.3 ms: 1.06x slower (+6%) |
+----------------------+--------------------------------------+-------------------------------------------------+
| scimark_monte_carlo | 197 ms | 210 ms: 1.07x slower (+7%) |
+----------------------+--------------------------------------+-------------------------------------------------+
| spectral_norm | 243 ms | 269 ms: 1.11x slower (+11%) |
+----------------------+--------------------------------------+-------------------------------------------------+
| sqlite_synth | 7.30 us | 8.13 us: 1.11x slower (+11%) |
+----------------------+--------------------------------------+-------------------------------------------------+
| unpickle_pure_python | 707 us | 796 us: 1.13x slower (+13%) |
+----------------------+--------------------------------------+-------------------------------------------------+
Not significant (55): 2to3; chameleon; chaos; (...)
Ничего себе, 5 тестов вниз. Регрессионное тестирование производительности популярно в Python: мы работаем над этимЗаставьте Python работать быстрее!
Пропустить неудачные тесты в канун Рождества
Я не ожидал, что 5 тестов снизят производительность. Это требует более глубокого изучения, но у меня нет на это времени, и я слишком застенчив/стыжусь, чтобы нести ответственность за регрессионное тестирование производительности.
Однако я не могу принять решение до рождественских каникул.test_embed.test_bpo20891()
Все еще случайный сбой на macOS, как всегда. То, что я добрался до наиболее подверженной ошибкам части Python — GIL, сильно ударило по мне за две недели до праздников. Поэтому я решил пропуститьtest_bpo20891()
модульное тестирование до окончания праздников.
Python 3.7, никаких пасхалок.
Запускаем новый бенчмарк, второе исправление слито в ветку master
В конце января 2018 года я снова провел 5 тестов, где производительность ухудшилась в моем PR. Я запускаю эти тесты вручную на своем ноутбуке, используя разные процессоры для разных тестов:
vstinner@apu$ python3 -m perf compare_to ref.json patch.json --table
Not significant (5): unpickle_pure_python; sqlite_synth; spectral_norm; pathlib; scimark_monte_carlo
Ну, согласноНабор тестов производительности Python, теперь доказывая, что мое второе исправление на самом деле неНе сильно влияет на производительность.
Я решил отправить исправление в ветку master, зафиксировать2914bb32:
bpo-20891: Py_Initialize() now creates the GIL (#4700)
The GIL is no longer created "on demand" to fix a race condition when
PyGILState_Ensure() is called in a non-Python thread.
Затем я перезапустил основную веткуtest_embed.test_bpo20891()
модульный тест.
К сожалению, второго исправления для Python 2.7 и 3.6 нет!
Antoine PitrouЗадумался о переносе патча на Python 3.6нельзя объединить:
Я не думаю, что это необходимо. Каждый может позвонить
PyEval_InitThreads()
.
Guido van RossumТакже не хочу портировать этот патч. Поэтому я удалил его из основной ветки 3.6.test_embed.test_bpo20891()
.
По этой же причине я тоже не стал применять свой второй патч в Python 2.7, к тому же в Python 2.7 нет юнит-тестов, потому что портирование слишком сложное.
Но, по крайней мере, Python 2.7 и 3.6 применили мой первый патч,PyGILState_Ensure()
.
Суммировать
Python по-прежнему имеет некоторые условия гонки в некоторых крайних случаях. Эта ошибка была обнаружена, когда поток C создал GIL с помощью Python API. Я нажал первый патч, но в macOS появилось еще одно новое состояние гонки.
Мне пришлось копаться в очень старой истории коммитов (1992 г.) Python GIL. к счастьюGuido van RossumМожет помочь найти основную причину ошибки вместе.
После сбоя в тесте мы согласились, что в Python 3.7 GIL всегда создается сразу после запуска парсера, а не «по требованию». Это изменение не оказало заметного влияния на производительность.
Мы также решили оставить Python 2.7 и 3.6 без изменений, чтобы предотвратить любой риск регрессионного тестирования: продолжайте создавать GIL «по запросу».
На исправление серьезной ошибки в знаменитой библиотеке Python GIL (Global Interpreter Lock) у меня ушло 4 года., Python GIL — одна из самых подверженных ошибкам частей Python. Я рад, что эта ошибка устранена: она полностью исправлена в грядущем выпуске Python 3.7!
существуетbpo-20891Посмотреть полную историю. Спасибо всем разработчикам, которые помогли мне исправить эту ошибку!
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.