Документация Python неофициальный перевод
Содержание страницы

Поддержка свободной многопоточности в PythonPython support for free threading

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

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

В этом документе описываются последствия свободной многопоточности для Python-кода. Информацию о том, как писать расширения на C, поддерживающие сборку со свободной многопоточностью, см. в C API Extension Support for Free Threading.

См. также

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

УстановкаInstallation

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

Информацию о других платформах см. в Installing a Free-Threaded Python, руководстве по установке, поддерживаемом сообществом, для установки Python со свободной многопоточностью.

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

Определение сборки со свободной потоковой обработкойIdentifying free-threaded Python

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

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

Глобальная блокировка интерпретатора в сборке со свободной потоковой обработкойThe global interpreter lock in free-threaded Python

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

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

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

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

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

Примечание

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

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

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

ИммортализацияImmortalization

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

По состоянию на версию 3.14 иммортализация ограничена:

  • Константами кода: числовыми литералами, строковыми литералами и литералами кортежей, состоящими из других констант.

  • Строками, интернированными с помощью sys.intern().

Объекты фреймовFrame objects

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

ИтераторыIterators

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

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

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

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

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

Контекстные переменныеContext variables

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

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

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

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

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

Все интернированные строки являются бессмертнымиAll interned strings are immortal

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

Объекты, не поддерживающие сборку мусора, имеют больший заголовок объектаNon-GC objects have a larger object header

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

QSBR может задерживать освобождение памятиQSBR can delay freeing of memory

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

Распределитель mimalloc против pymallocmimalloc allocator vs pymalloc

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

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

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

Подсчёт ссылок в свободнопоточной сборке может приводить к более долгому времени жизни объектовFree-threaded reference counting can cause objects to live longer

В стандартной сборке, когда счётчик ссылок объекта достигает нуля, он обычно освобождается. Сборка со свободными потоками использует «смещённый подсчёт ссылок» (biased reference counting) с быстрым путём для объектов, «принадлежащих» текущему потоку, и медленным путём для других объектов. См. PEP 703 для получения дополнительных сведений. Когда счётчик ссылок объекта оказывается в состоянии «в очереди», освобождение может быть отложено. Состояние очереди сбрасывается в секции «eval breaker» интерпретатора байткода.

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

  • объекты модулей

  • функции верхнего уровня модулей

  • методы классов, определённые в пространстве имён класса

  • объекты дескрипторов

  • потоково-локальные объекты, созданные с помощью threading.local

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

Попоточный подсчёт ссылок может задерживать освобождение объектовPer-thread reference counting can delay freeing objects

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

  • объекты типов кучи (классы, определённые в Python)

  • объекты кода

  • словарь __dict__ объектов модулей

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