Как работают потоки в Python и какие общие ошибки Python-threading?

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

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

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

Ответ 1

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

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

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

Если вы хотите использовать многоядерные процессоры, pyprocessing определяет API, основанный на процессах, для выполнения реальной распараллеливания.

Ответ 2

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

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

Если вы хотите улучшить отзывчивость, вы должны использовать CONSIDER с помощью потоков. Существуют и другие альтернативы, а именно microthreading. Существуют также некоторые рамки, на которые вы должны обратить внимание:

Ответ 3

Ниже приведен пример базового потока. Он будет создавать 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...

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

Ответ 4

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

Ответ 5

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

Потоки работают немного по-другому в 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()

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

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

Which thread to switch to next?

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

Ответ 6

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

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

Ответ 7

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

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