Spec-Zone.ru › Python 3.14

Поддержка свободной многопоточности в Python

Начиная с выпуска 3.13, CPython поддерживает сборку Python, называемую свободной многопоточностью, в которой отключена глобальная блокировка интерпретатора (GIL). Выполнение без GIL позволяет полностью использовать доступные вычислительные мощности, запуская потоки параллельно на доступных ядрах ЦП. Хотя не всё программное обеспечение автоматически получит от этого пользу, программы, разработанные с учётом многопоточности, будут работать быстрее на многоядерном оборудовании.

Некоторые сторонние пакеты, особенно те, которые содержат модуль расширения, могут быть не готовы к использованию в сборке со свободной многопоточностью и повторно включат GIL.

В этом документе описано влияние свободной многопоточности на код Python. Информацию о написании расширений C, поддерживающих сборку со свободной многопоточностью, см. в разделе Поддержка свободной многопоточности в расширениях C API.

См. также

PEP 703 — общий обзор свободной многопоточности в Python: «Сделать глобальную блокировку интерпретатора необязательной в CPython».

Установка

Начиная с Python 3.13, официальные установщики для macOS и Windows позволяют при необходимости установить двоичные файлы Python со свободной многопоточностью. Установщики доступны по адресу https://www.python.org/downloads/.

Сведения о других платформах см. в руководстве Установка Python со свободной многопоточностью — руководстве по установке Python со свободной многопоточностью, которое поддерживается сообществом.

При сборке CPython из исходного кода для создания интерпретатора Python со свободной многопоточностью следует использовать параметр настройки --disable-gil.

Как определить Python со свободной многопоточностью

Чтобы проверить, поддерживает ли текущий интерпретатор свободную многопоточность, проверьте наличие фразы «free-threading build» в python -VV и sys.version. Новая функция sys._is_gil_enabled() позволяет проверить, действительно ли GIL отключена в работающем процессе.

Переменную конфигурации sysconfig.get_config_var("Py_GIL_DISABLED") можно использовать, чтобы определить, поддерживает ли сборка свободную многопоточность. Если переменная имеет значение 1, сборка поддерживает свободную многопоточность. Это рекомендуемый способ принимать решения, связанные с конфигурацией сборки.

Глобальная блокировка интерпретатора в Python со свободной многопоточностью

Сборки CPython со свободной многопоточностью позволяют при необходимости запускать интерпретатор с включённой GIL во время выполнения, используя переменную окружения PYTHON_GIL или параметр командной строки -X gil.

GIL также может включаться автоматически при импорте модуля расширения C API, который явно не помечен как поддерживающий свободную многопоточность. В этом случае будет выведено предупреждение.

Помимо документации отдельных пакетов, на следующих сайтах отслеживается состояние поддержки свободной многопоточности популярными пакетами:

  • https://py-free-threading.github.io/tracking/
  • https://hugovk.github.io/free-threaded-wheels/

Потокобезопасность

Цель сборки CPython со свободной многопоточностью — обеспечить на уровне Python поведение, связанное с потокобезопасностью, аналогичное поведению стандартной сборки с включённой GIL. Встроенные типы, такие как dict, list и set, используют внутренние блокировки для защиты от одновременных изменений, поведение которых аналогично GIL. Однако Python исторически не гарантировал определённого поведения при одновременном изменении этих встроенных типов, поэтому это следует считать описанием текущей реализации, а не гарантией текущего или будущего поведения.

Примечание

По возможности рекомендуется использовать threading.Lock или другие примитивы синхронизации, а не полагаться на внутренние блокировки встроенных типов.

Известные ограничения

В этом разделе описаны известные ограничения сборки CPython со свободной многопоточностью.

Бессмертие объектов

В сборке со свободной многопоточностью некоторые объекты являются бессмертными. Бессмертные объекты не освобождаются, а их счётчики ссылок никогда не изменяются. Это сделано для предотвращения конкуренции при обновлении счётчиков ссылок, которая мешала бы эффективному масштабированию на нескольких потоках.

Начиная с выпуска 3.14, бессмертие применяется только к следующим объектам:

  • Константы кода: числовые литералы, строковые литералы и литералы кортежей, составленные из других констант.
  • Строки, интернированные функцией sys.intern().

Объекты кадров

Небезопасно обращаться к frame.f_locals объекта кадра, если этот кадр в данный момент выполняется в другом потоке; такие действия могут привести к аварийному завершению интерпретатора.

Итераторы

Как правило, небезопасно одновременно обращаться к одному объекту-итератору из нескольких потоков: потоки могут получать повторяющиеся или пропущенные элементы.

Производительность в однопоточном режиме

При выполнении кода Python сборка со свободной многопоточностью создаёт дополнительную нагрузку по сравнению со стандартной сборкой с включённой GIL. Её величина зависит от рабочей нагрузки и оборудования. В наборе тестов производительности pyperformance средняя дополнительная нагрузка составляет от примерно 1% на macOS aarch64 до 8% на системах Linux с x86-64.

Изменения поведения

В этом разделе описаны изменения поведения CPython в сборке со свободной многопоточностью.

Переменные контекста

В сборке со свободной многопоточностью флаг thread_inherit_context по умолчанию установлен в true, поэтому потоки, созданные с помощью threading.Thread, запускаются с копией Context() вызывающего start(). В стандартной сборке с включённой GIL по умолчанию флаг имеет значение false, поэтому потоки запускаются с пустым Context().

Фильтры предупреждений

В сборке со свободной многопоточностью флаг context_aware_warnings по умолчанию установлен в true. В стандартной сборке с включённой GIL по умолчанию флаг имеет значение false. Если флаг установлен в true, менеджер контекста warnings.catch_warnings использует переменную контекста для фильтров предупреждений. Если флаг установлен в false, catch_warnings изменяет глобальный список фильтров, что небезопасно при работе с потоками. Дополнительные сведения см. в модуле warnings.

Увеличенное потребление памяти

Сборка со свободной многопоточностью обычно использует больше памяти, чем стандартная сборка. Причин этому несколько, в основном они связаны с принятыми проектными решениями.

Все интернированные строки бессмертны

В современных версиях Python (начиная с версии 2.3) интернирование строки (например, с помощью sys.intern()) не делает её бессмертной. Если последняя ссылка на такую строку исчезает, она удаляется из таблицы интернированных строк. В сборке со свободной многопоточностью это не так: любая интернированная строка становится бессмертной и сохраняется до завершения работы интерпретатора.

Объекты, не отслеживаемые сборщиком мусора, имеют больший заголовок

В сборке со свободной многопоточностью используется другая структура PyObject. В стандартной сборке сведения, связанные со сборщиком мусора, выделяются перед структурой PyObject, а в сборке со свободной многопоточностью они входят в обычный заголовок объекта. Например, на платформе AMD64 None занимает 32 байта в сборке со свободной многопоточностью и 16 байт в стандартной сборке. Объекты, отслеживаемые сборщиком мусора (например, словари и списки), имеют одинаковый размер в обеих сборках, поскольку сборка со свободной многопоточностью не выделяет дополнительное пространство для сведений о сборщике мусора.

QSBR может задерживать освобождение памяти

Для безопасной реализации структур данных без блокировок используется схема безопасного освобождения памяти (SMR), известная как освобождение на основе состояния покоя (QSBR). Это означает, что для структур данных, обеспечивающих доступ без блокировок, используется QSBR, которая откладывает освобождение памяти вместо того, чтобы освобождать её немедленно. Два примера таких структур данных — объект списка и объект ключей словаря. Дополнительные сведения о реализации QSBR см. в InternalDocs/qsbr.md в дереве исходного кода CPython. Вызов gc.collect() должен привести к фактическому освобождению всей памяти, удерживаемой QSBR. Учтите, что даже после освобождения памяти механизмом QSBR используемый распределитель памяти может не сразу вернуть её операционной системе, поэтому размер резидентного набора (RSS) процесса может не уменьшиться.

Распределитель mimalloc и pymalloc

Стандартная сборка обычно использует распределитель памяти «pymalloc» для небольших выделений памяти (512 байт или меньше). Сборка со свободной многопоточностью не использует pymalloc и выделяет все объекты Python с помощью распределителя «mimalloc». Распределитель pymalloc обладает следующими свойствами, помогающими уменьшить потребление памяти: небольшие накладные расходы на каждый выделенный блок, эффективное предотвращение фрагментации памяти и быстрое возвращение свободной памяти операционной системе. Распределитель mimalloc также хорошо справляется с этими задачами, но может создавать несколько большие накладные расходы.

В сборке со свободной многопоточностью mimalloc управляет памятью с помощью нескольких отдельных куч (в настоящее время их четыре). Например, все объекты, поддерживающие сборку мусора, выделяются из собственной кучи. Из-за разделения на кучи свободную память одной кучи нельзя использовать для выделения памяти из другой. Кроме того, некоторые кучи настроены на использование QSBR (освобождения на основе состояния покоя) при освобождении памяти, резервируемой кучей (в терминологии mimalloc она называется «страницами»). Использование QSBR создаёт задержку между освобождением всех блоков памяти страницы и освобождением самой страницы — для новых выделений памяти или возврата операционной системе.

Распределитель mimalloc также откладывает возврат освобождённой памяти операционной системе. Эту задержку можно уменьшить, установив переменную окружения MIMALLOC_PURGE_DELAY в 0. Учтите, что это, вероятно, снизит производительность распределителя.

Подсчёт ссылок в режиме свободной многопоточности может продлевать срок жизни объектов

В стандартной сборке объект обычно освобождается, когда его счётчик ссылок достигает нуля. В сборке со свободной многопоточностью используется «смещённый подсчёт ссылок»: быстрый путь для объектов, «принадлежащих» текущему потоку, и медленный путь для остальных объектов. Дополнительные сведения см. в PEP 703. Если счётчик ссылок объекта оказывается в состоянии «в очереди», освобождение объекта может быть отложено. Состояние очереди сбрасывается в разделе «eval breaker» вычислителя байт-кода.

В сборке со свободной многопоточностью также доступен другой режим подсчёта ссылок, называемый «отложенным подсчётом ссылок». Этот режим включается установкой флага для отдельного объекта. Отложенный подсчёт ссылок включён для следующих типов:

  • объекты модулей
  • функции верхнего уровня модуля
  • методы классов, определённые в области видимости класса
  • объекты-дескрипторы
  • объекты локальных переменных потока, созданные с помощью threading.local

При включённом отложенном подсчёте ссылок ссылки со стеков функций Python не учитываются в счётчике ссылок. Эта схема уменьшает накладные расходы на подсчёт ссылок, особенно для объектов, используемых несколькими потоками. Поскольку ссылки в стеках не учитываются, объекты с отложенным подсчётом ссылок не освобождаются немедленно, когда их внутренний счётчик ссылок достигает нуля. Вместо этого они проверяются при следующем запуске сборщика мусора и освобождаются, если ссылки на них в стеках не обнаружены. Таким образом, эти объекты освобождаются сборщиком мусора, а не при обнулении счётчика ссылок, как обычно.

Подсчёт ссылок в каждом потоке может задерживать освобождение объектов

Чтобы избежать конкуренции при доступе к полям счётчика ссылок часто используемых совместно объектов, в сборке со свободной многопоточностью для нескольких выбранных типов объектов также применяется «подсчёт ссылок в каждом потоке». Вместо обновления единого общего счётчика ссылок каждый поток поддерживает собственный локальный массив счётчиков, индексируемый уникальным идентификатором, присвоенным объекту. Фактическое значение счётчика ссылок вычисляется только при обнулении локального счётчика объекта — для этого суммируются счётчики всех потоков. В настоящее время подсчёт ссылок в каждом потоке применяется для:

  • объектов типов в куче (классов, созданных в Python)
  • объектов кода
  • объекта __dict__ модулей

Поскольку перед освобождением объекта счётчики потоков необходимо объединить с его счётчиком, объекты с подсчётом ссылок в каждом потоке обычно освобождаются позже, чем в стандартной сборке. В частности, такой объект обычно не освобождается до тех пор, пока поток, который на него ссылался, не достигнет безопасной точки (например, раздела «eval breaker» вычислителя байт-кода) или не завершится. Вызов gc.collect() объединит счётчики потоков и позволит освободить эти объекты.

© 2001 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.14/howto/free-threading-python.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API