Как потоки работают в Python и каковы общие подводные камни Python-threading?

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

Насколько я могу судить, одновременно может работать только один поток, а активный поток переключается каждые 10 инструкций или около того?

Где есть хорошее объяснение или вы можете его предоставить? Также было бы очень хорошо знать об общих проблемах, с которыми вы сталкиваетесь при использовании потоков с Python.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
85
0
40 868
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Ниже приведен базовый образец резьбы. Будет создано 20 потоков; каждый поток выведет свой номер потока. Запустите его и соблюдайте порядок печати.

import threading
class Foo (threading.Thread):
    def __init__(self,x):
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):
          print str(self.__x)

for x in xrange(20):
    Foo(x).start()

Как вы уже намекали, потоки Python реализуются посредством квантования времени. Так получается «параллельный» эффект.

В моем примере мой класс Foo расширяет поток, затем я реализую метод run, в котором идет код, который вы хотите запустить в потоке. Чтобы запустить поток, вы вызываете start() для объекта потока, который автоматически вызывает метод run ...

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

Python - это довольно простой язык для внедрения, но с некоторыми оговорками. Самое важное, о чем вам нужно знать, - это Global Interpreter Lock. Это позволяет только одному потоку получить доступ к интерпретатору. Это означает две вещи: 1) вы редко когда-либо используете оператор блокировки в python и 2) если вы хотите использовать преимущества многопроцессорных систем, вы должны использовать отдельные процессы. Обновлено: Я также должен указать, что вы можете поместить часть кода на C / C++, если хотите также обойти GIL.

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

Если вы хотите улучшить отзывчивость, вам следует РАССМАТРИВАТЬ использование потоков. Однако есть и другие альтернативы, а именно микронить. Также вам следует изучить несколько фреймворков:

@JS - Исправлено. В любом случае этот список устарел.

Jason Baker 28.06.2013 00:25

Мне просто кажется неправильным, что вам нужно несколько процессов - со всеми вытекающими отсюда накладными расходами - чтобы воспользоваться преимуществами многоядерной системы. У нас есть серверы с 32 логическими ядрами - так мне нужно 32 процесса, чтобы их эффективно использовать? Безумие

Basic 12.08.2013 16:57

@Basic - накладные расходы при запуске процесса по сравнению с запуском потока в наши дни минимальны. Я полагаю, вы можете начать видеть проблемы, если мы говорим о тысячах запросов в секунду, но тогда я бы поставил под вопрос выбор Python для такой загруженной службы в первую очередь.

Jason Baker 17.08.2013 20:02
Ответ принят как подходящий

Да, из-за глобальной блокировки интерпретатора (GIL) одновременно может выполняться только один поток. Вот несколько ссылок с некоторыми сведениями об этом:

Из последней ссылки интересная цитата:

Let me explain what all that means. Threads run inside the same virtual machine, and hence run on the same physical machine. Processes can run on the same physical machine or in another physical machine. If you architect your application around threads, you’ve done nothing to access multiple machines. So, you can scale to as many cores are on the single machine (which will be quite a few over time), but to really reach web scales, you’ll need to solve the multiple machine problem anyway.

Если вы хотите использовать многоядерность, пипроцессинг определяет API на основе процессов для реального распараллеливания. PEP также включает несколько интересных тестов.

Действительно комментарий к цитате smoothspan: конечно, потоки Python эффективно ограничивают вас одним ядром, даже если на машине их несколько? Многоядерность может принести пользу, поскольку следующий поток может быть готов к работе без переключения контекста, но ваши потоки Python никогда не могут использовать более 1 ядра за раз.

James Brady 27.12.2008 03:53

Правильно, потоки python практически ограничены одним ядром, ЕСЛИ модуль C не взаимодействует с GIL и запускает собственный собственный поток.

Arafangion 18.02.2009 09:44

Фактически, несколько ядер делают потоки меньше эффективными, так как возникает много проблем с проверкой того, может ли каждый поток получить доступ к GIL. Даже с новым GIL производительность все равно хуже ... dabeaz.com/python/NewGIL.pdf

Basic 12.08.2013 16:55

Обратите внимание, что рекомендации GIL не относятся ко всем переводчикам. Насколько мне известно, и IronPython, и Jython работают без GIL, что позволяет их коду более эффективно использовать многопроцессорное оборудование. Как упоминал Арафангион, интерпретатор CPython также может правильно работать в многопоточном режиме, если код, которому не требуется доступ к элементам данных Python, снимает блокировку, а затем снова получает ее перед возвратом.

holdenweb 23.02.2014 04:14

Что вызывает переключение контекста между потоками в Python? Это основано на прерываниях таймера? Блокировка или конкретный вызов доходности?

CMCDragonkai 10.10.2016 16:17

Используйте потоки в Python, если отдельные воркеры выполняют операции ввода-вывода. Если вы пытаетесь масштабировать несколько ядер на машине, либо найдите хороший фреймворк МПК для python, либо выберите другой язык.

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

Я бы пошел так далеко, что предложил несколько родительских процессоров и попытался бы сохранить одинаковые рабочие места на одном ядре (ах).

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

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

Примечание: везде, где я упоминаю thread, я имею в виду конкретно потоки в Python, пока явно не указано иное.

В python потоки работают немного по-другому, если вы работаете с C/C++. В python только один поток может находиться в состоянии выполнения в данный момент времени. Это означает, что потоки в python не могут по-настоящему использовать мощность нескольких ядер обработки, поскольку по дизайну потоки не могут выполняться параллельно на нескольких ядрах.

Поскольку управление памятью в Python не является потокобезопасным, каждый поток требует монопольного доступа к структурам данных в интерпретаторе Python. Этот монопольный доступ обеспечивается механизмом GIL(глобальная блокировка интерпретатора).

Why does python use GIL?

Чтобы предотвратить одновременный доступ нескольких потоков к состоянию интерпретатора и его повреждение.

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

Why not simply remove GIL?

Дело не в том, что удалить GIL невозможно, просто при этом мы в конечном итоге устанавливаем несколько блокировок внутри интерпретатора для сериализации доступа, что делает даже однопоточное приложение менее производительным.

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

So when does thread switching occurs in python?

Переключение потоков происходит при выпуске GIL. Итак, когда выпускается GIL? Следует принять во внимание два сценария.

Если поток выполняет операции с привязкой к ЦП (например, обработка изображений).

В более старых версиях python переключение потоков происходило после фиксированного количества инструкций python. По умолчанию для него было установлено значение 100. Оказалось, что это не очень хорошая политика, чтобы решать, когда переключение должно происходить, поскольку время, затрачиваемое на выполнение одного инструкция может очень дико, от миллисекунды до даже секунды. Поэтому выпуск GIL после каждой инструкции 100, независимо от времени, необходимого для их выполнения, является плохой политикой.

В новых версиях вместо использования количества инструкций в качестве метрики для переключения потока используется настраиваемый временной интервал. Интервал переключения по умолчанию составляет 5 миллисекунд. Вы можете получить текущий интервал переключения, используя sys.getswitchinterval(). Это можно изменить с помощью sys.setswitchinterval()

Если поток выполняет некоторые операции с привязкой к вводу-выводу (доступ к файловой системе Ex или
сеть IO)

GIL освобождается всякий раз, когда поток ожидает завершения некоторой операции ввода-вывода.

Which thread to switch to next?

У интерпретатора нет собственного планировщика. Какой поток станет запланированным в конце интервала, решает операционная система. .

Другие вопросы по теме