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






Ниже приведен базовый образец резьбы. Будет создано 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.
Таким образом, вам нужно пересмотреть, почему вы хотите использовать потоки. Если вы хотите распараллелить свое приложение, чтобы воспользоваться преимуществами двухъядерной архитектуры, вам необходимо подумать о том, чтобы разбить приложение на несколько процессов.
Если вы хотите улучшить отзывчивость, вам следует РАССМАТРИВАТЬ использование потоков. Однако есть и другие альтернативы, а именно микронить. Также вам следует изучить несколько фреймворков:
Мне просто кажется неправильным, что вам нужно несколько процессов - со всеми вытекающими отсюда накладными расходами - чтобы воспользоваться преимуществами многоядерной системы. У нас есть серверы с 32 логическими ядрами - так мне нужно 32 процесса, чтобы их эффективно использовать? Безумие
@Basic - накладные расходы при запуске процесса по сравнению с запуском потока в наши дни минимальны. Я полагаю, вы можете начать видеть проблемы, если мы говорим о тысячах запросов в секунду, но тогда я бы поставил под вопрос выбор Python для такой загруженной службы в первую очередь.
Да, из-за глобальной блокировки интерпретатора (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 ядра за раз.
Правильно, потоки python практически ограничены одним ядром, ЕСЛИ модуль C не взаимодействует с GIL и запускает собственный собственный поток.
Фактически, несколько ядер делают потоки меньше эффективными, так как возникает много проблем с проверкой того, может ли каждый поток получить доступ к GIL. Даже с новым GIL производительность все равно хуже ... dabeaz.com/python/NewGIL.pdf
Обратите внимание, что рекомендации GIL не относятся ко всем переводчикам. Насколько мне известно, и IronPython, и Jython работают без GIL, что позволяет их коду более эффективно использовать многопроцессорное оборудование. Как упоминал Арафангион, интерпретатор CPython также может правильно работать в многопоточном режиме, если код, которому не требуется доступ к элементам данных Python, снимает блокировку, а затем снова получает ее перед возвратом.
Что вызывает переключение контекста между потоками в Python? Это основано на прерываниях таймера? Блокировка или конкретный вызов доходности?
Используйте потоки в 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?
У интерпретатора нет собственного планировщика. Какой поток станет запланированным в конце интервала, решает операционная система. .
@JS - Исправлено. В любом случае этот список устарел.