Потоки в Python

Каковы модули, используемые для написания многопоточных приложений в Python? Я знаю основные concurrency механизмы, предоставляемые языком, а также Stackless Python, но каковы их сильные и слабые стороны?

Ответ 1

В порядке возрастания сложности:

Используйте модуль потоковой передачи

Плюсы:

  • Очень легко запускать любую функцию (любую вызываемую на самом деле) в ее собственный поток.
  • Обмен данными - это нелегко (блокировки никогда не бывают легкими:), при наименее простой.

Минусы:

  • Как упоминалось by Juergen Нити Python не могут фактически одновременно получить доступ к состоянию в интерпретаторе (там один большой замок, печально известный Global Interpreter Lock.) На практике это означает, что потоки полезны для задач, связанных с I/O (создание сетей, запись на диск и т.д.), но совсем не полезно для параллельных вычислений.

Используйте multiprocessing модуль

В простом случае это выглядит точно так же, как с помощью threading, за исключением того, что каждая задача выполняется в своем собственном процессе, а не в своем потоке. (Почти буквально: если вы берете пример Eli и заменяете threading на multiprocessing, Thread, с Process и Queue (модуль ) с multiprocessing.Queue, он должен работать нормально.)

Плюсы:

  • Фактический concurrency для всех задач (без блокировки Global Interpreter).
  • Масштабирует до нескольких процессоров, может даже масштабироваться до нескольких компьютеров.

Минусы:

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

Используйте модель событий, например Twisted

Плюсы:

  • Вы получаете чрезвычайно точный контроль над приоритетом, над тем, что выполняется, когда.

Минусы:

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

В всех случаях я предполагаю, что вы уже понимаете многие проблемы, связанные с многозадачностью, в частности сложная проблема обмена данными между задачами. Если по какой-то причине вы не знаете, когда и как использовать блокировки и условия, вы должны начать с них. Многозадачный код полон тонкостей и gotchas, и лучше всего иметь хорошее понимание понятий, прежде чем вы начнете.

Ответ 2

Вы уже получили множество ответов, от "поддельных потоков" до внешних фреймворков, но я не видел, чтобы никто не упоминал Queue.Queue - "секретный соус" потоковой обработки CPython.

Чтобы развернуть: до тех пор, пока вам не нужно перекрывать чистую процессорную обработку с чистым Python (в этом случае вам нужно multiprocessing), но оно также имеет собственную реализацию Queue, поэтому вы можете с некоторыми необходимыми предостережениями применяют общие рекомендации, которые я даю;-), Python встроенный threading будет делать... но он будет делать это намного лучше, если вы его используете, например, следующим образом.

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

Посвятите специализированный поток каждому ресурсу, который вы обычно считаете защищенным блокировками: изменчивая структура данных или их целая группа, соединение с внешним процессом (БД, сервер XMLRPC и т.д.), внешний файл, и т.д. и т.д. Получите небольшой пул потоков, предназначенный для задач общего назначения, которые не имеют или не требуют выделенного ресурса такого рода - не создавайте потоки как и когда это необходимо, или накладные расходы на переключение потоков будут перегружать вас.

Связь между двумя потоками всегда осуществляется через Queue.Queue - форма передачи сообщений, единственная нормальная основа для многопроцессорности (помимо транзакционной памяти, которая является многообещающей, но для которой я не знаю никаких достойных производства реализаций, кроме In Haskell).

Каждый выделенный поток, управляющий единственным ресурсом (или небольшим сплоченным набором ресурсов), прослушивает запросы на конкретном экземпляре Queue.Queue. Потоки в пуле ждут на одной общей Queue.Queue(Очередь прочно потокобезопасна и не подведет вас).

Потоки, которые просто нужно поставить в очередь на запрос в некоторой очереди (общий или выделенный), делают это, не дожидаясь результатов, и двигайтесь дальше. Темы, которые в конечном итоге требуют результата или подтверждения для очереди запросов пары (request, receivequeue) с экземпляром Queue.Queue, который они только что сделали, и в конце концов, когда ответ или подтверждение необходимы для продолжения, они получают (ожидание ) от их получения. Убедитесь, что вы готовы получить ответы об ошибках, а также реальные ответы или подтверждения (Twisted deferred отлично подходят для организации такого рода структурированного ответа, BTW!).

Вы также можете использовать Queue для "парковки" экземпляров ресурсов, которые могут использоваться каким-либо одним потоком, но никогда не должны использоваться совместно несколькими потоками за один раз (соединения с DB с некоторыми компиляторами DBAPI, курсорами с другими и т.д.) - это позволяет вам избавиться от требований выделенных потоков в пользу большего объединения (поток пула, который поступает из общей очереди, запрос, требующий ресурса, доступного для очереди, получит этот ресурс из подходящей очереди, ожидая при необходимости и т.д. и т.д.).

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

Но я понимаю, что Twisted не для всех - "посвятить или объединить ресурсы, использовать Queue up wazoo, никогда не делать ничего, что нужно Lock или, Guido запретить, любую процедуру синхронизации еще более продвинутую, такую ​​как семафор или условие" подход все еще можно использовать, даже если вы просто не можете обернуть голову асинхронными методологиями, основанными на событиях, и будете по-прежнему обеспечивать большую надежность и производительность, чем любой другой широко применимый подход к потокам, который я когда-либо наткнулся.

Ответ 3

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

from threading import Thread

def f():
    ...

def g(arg1, arg2, arg3=None):
    ....

Thread(target=f).start()
Thread(target=g, args=[5, 6], kwargs={"arg3": 12}).start()

И так далее. У меня часто есть настройка производителя/потребителя, использующая синхронизированную очередь, предоставляемую модулем Queue

from Queue import Queue
from threading import Thread

q = Queue()
def consumer():
    while True:
        print sum(q.get())

def producer(data_source):
    for line in data_source:
        q.put( map(int, line.split()) )

Thread(target=producer, args=[SOME_INPUT_FILE_OR_SOMETHING]).start()
for i in range(10):
    Thread(target=consumer).start()

Ответ 4

Kamaelia - это среда разработки Python для создания приложений с множеством взаимодействующих процессов.

UMeia.png

(источник: kamaelia.org) Kamaelia - Параллелизм стал полезным, веселым

В Kamaelia вы строите системы из простых компонентов, которые взаимодействуют друг с другом. Это ускоряет разработку, значительно облегчает обслуживание, а также означает, что вы создаете естественно параллельное программное обеспечение. Он предназначен для доступа любому разработчику, включая новичков. Это также делает это забавным :)

Какие системы? Сетевые серверы, клиенты, настольные приложения, игры на основе Pygame, системы перекодировки и конвейеры, системы цифрового телевидения, средства искоренения спама, средства обучения и многое другое :)

Вот видео из Pycon 2009. Он начинается со сравнения Kamaelia с Twisted и Parallel Python, а затем дает демонстрацию Kamaelia.

Легкий параллелизм с Камаелией - Часть 1 (59:08)
Легкий параллелизм с Камаелией - Часть 2 (18:15)

Ответ 5

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

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

"Не совершенная" часть связана с тем, что синтаксический сахар еще не добавлен для почтовых ящиков и почтовых ящиков (хотя это обсуждается). В системе сосредоточено внимание на безопасности/удобстве использования.

Взяв пример производителя-производителя, используя голой поток, это становится в Kamaelia:

Pipeline(Producer(), Consumer() )

В этом примере не имеет значения, являются ли они потоковыми компонентами или иным образом, единственное различие между ними с точки зрения использования - это базовый класс для компонента. Компоненты генератора взаимодействуют с использованием списков, потоковых компонентов с использованием Queue.Queues и процесса, основанного на использовании os.pipes.

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

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

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

Соответствующие ссылки:

В любом случае, я надеюсь, что это полезный ответ. FWIW, основной причиной настройки Kamaelia является сделать concurrency более безопасным и простым в использовании в системах python, без того, чтобы хвост вилял собаку. (т.е. большой ковш компонентов

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

И чтобы мой способ сказать, пожалуйста, предостерегаю, что этот ответ по определению предвзято, но для меня цель Камаэлии - попытаться обернуть то, что является лучшей практикой ИМО. Я бы предложил попробовать несколько систем и посмотреть, что работает для вас. (также, если это неуместно для, извините - я новичок в этом форуме: -)

Ответ 6

Я бы использовал Microthreads (Tasklets) Stackless Python, если бы мне пришлось использовать потоки вообще.

Вся онлайн-игра (массовая многопользовательская) строится вокруг Stackless и ее многопотокового принципа - поскольку оригинал просто замедляет массовое многопользовательское свойство игры.

Темы в CPython широко обескуражены. Одной из причин является GIL - глобальная блокировка интерпретатора, которая сериализует потоки для многих частей выполнения. Мой опыт заключается в том, что создавать быстрые приложения так сложно. Мои примеры кодировок, где все медленнее с потоком - с одним ядром (но многие ожидания ввода должны были сделать некоторые повышения производительности возможными).

С CPython, скорее, используйте отдельные процессы, если это возможно.

Ответ 7

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

Одно из преимуществ, которое вы обнаружите, состоит в том, что в большинстве случаев вам не понадобятся блокировки или мьютексы при использовании совместной многозадачности, но для меня важнее было почти нулевую скорость переключения между "потоками". Конечно, Stackless Python, как говорят, очень хорош для этого; а затем Erlang, если он не должен быть Python.

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

После того, как вы сделали еще немного сложное приложение с фальшивыми сопрограммами, вы действительно начнете ценить работу, которая идет на планирование процесса на уровне ОС.