Потоки в Python
Какие модули используются для написания многопоточных приложений на Python? Я знаю об основных механизмах параллелизма, предоставляемых языком, а также о Stackless Python, но каковы их сильные и слабые стороны?
7 ответов
В порядке возрастания сложности:
Используйте модуль потоков
Плюсы:
- Запустить любую функцию (на самом деле вызываемую на самом деле) очень просто в ее собственном потоке.
- Обмен данными если не просто (блокировки никогда не бывает легким:), по крайней мере, простым.
Минусы:
- Как упоминал Юрген, потоки Python не могут фактически одновременно обращаться к состоянию в интерпретаторе (есть одна большая блокировка, печально известная глобальная блокировка интерпретатора.) На практике это означает, что потоки полезны для задач, связанных с вводом / выводом (работа в сети, запись на диск, и так далее), но совсем не полезно для выполнения параллельных вычислений.
Используйте многопроцессорный модуль
В простом случае использования это выглядит точно так же, как использование threading
за исключением того, что каждая задача выполняется в своем собственном процессе, а не в своем собственном потоке. (Почти буквально: если вы возьмете пример Эли и замените threading
с multiprocessing
, Thread
, с Process
, а также Queue
(модуль) с multiprocessing.Queue
, он должен работать просто отлично.)
Плюсы:
- Фактический параллелизм для всех задач (без Global Interpreter Lock).
- Масштабируется до нескольких процессоров, может даже масштабироваться до нескольких машин.
Минусы:
- Процессы медленнее, чем потоки.
- Обмен данными между процессами сложнее, чем с потоками.
- Память не является неявно разделяемой. Вы должны либо явно поделиться им, либо выбирать переменные и отправлять их туда и обратно. Это безопаснее, но сложнее. (Если это имеет значение, разработчики Python, похоже, подталкивают людей в этом направлении.)
Используйте модель событий, такую как Twisted
Плюсы:
- Вы получаете очень хороший контроль над приоритетом, над тем, что выполняется, когда.
Минусы:
- Даже с хорошей библиотекой асинхронное программирование обычно сложнее, чем многопоточное программирование, сложное как с точки зрения понимания того, что должно происходить, так и с точки зрения отладки того, что происходит на самом деле.
Во всех случаях я предполагаю, что вы уже понимаете многие проблемы, связанные с многозадачностью, в частности, сложный вопрос о том, как распределять данные между задачами. Если по какой-то причине вы не знаете, когда и как использовать блокировки и условия, вы должны начать с них. Многозадачный код полон тонкостей и ошибок, и действительно лучше иметь хорошее понимание концепций, прежде чем начать.
Вы уже получили достаточно разнообразных ответов, от "фальшивых тем" вплоть до внешних фреймворков, но я не видел никого, кто бы упомянул Queue.Queue
- "секретный соус" потоков CPython.
Расширение: до тех пор, пока вам не нужно перекрывать чисто Python-процессорную обработку (в этом случае вам нужно multiprocessing
- но это приходит со своим Queue
реализации, так что вы можете с некоторыми необходимыми предостережениями применить общий совет, который я даю;-), встроенный в Python threading
будет делать... но он будет делать это намного лучше, если вы будете использовать его осознанно, например, следующим образом.
"Забудьте" разделяемую память, предположительно, главный плюс многопоточности против многопоточности - она не работает хорошо, она плохо масштабируется, никогда не будет и никогда не будет. Используйте разделяемую память только для структур данных, которые настраиваются один раз перед тем, как вы порождаете подпотоки, и никогда не менялись впоследствии - для всего остального, возьмите на себя ответственность за этот ресурс за один поток и обменивайтесь данными с этим потоком через Queue
,
Выделите специализированный поток для каждого ресурса, который вы обычно думаете защищать с помощью блокировок: изменяемая структура данных или их связная группа, подключение к внешнему процессу (БД, сервер XMLRPC и т. Д.), Внешний файл и т. Д. И т. Д. И т. Д. Получите небольшой пул потоков, предназначенный для задач общего назначения, которые не имеют или не нуждаются в выделенном ресурсе такого рода - не создавайте потоки по мере необходимости, иначе издержки на переключение потоков будут перегружать вас.
Связь между двумя потоками всегда через Queue.Queue
- форма передачи сообщений, единственная разумная основа для многопроцессорной обработки (кроме многообещающей транзакционной памяти, но для которой я не знаю ни одной производственной реализации, кроме In Haskell).
Каждый выделенный поток, управляющий одним ресурсом (или небольшим связным набором ресурсов), прослушивает запросы к конкретному экземпляру Queue.Queue. Потоки в пуле ждут в одной общей очереди. Очередь (очередь полностью безопасна для потоков и не подведет вас в этом).
Потоки, которым просто нужно поставить в очередь запрос в какой-либо очереди (общей или выделенной), делают это, не дожидаясь результатов, и продолжают работу. Потоки, которые в конечном итоге ДОЛЖНЫ получить результат или подтверждение для очереди запросов, пары (request, receivequeue) с экземпляром Queue.Queue, который они только что создали, и, в конце концов, когда ответ или подтверждение необходимы для продолжения, они получают (ожидание)) из их получения. Убедитесь, что вы готовы получать сообщения об ошибках, а также реальные ответы или подтверждения (Twisted's deferred
Кстати, они отлично справляются с организацией такого структурированного ответа, кстати!).
Вы также можете использовать Очередь для "парковки" экземпляров ресурсов, которые могут использоваться любым одним потоком, но никогда не могут совместно использоваться несколькими потоками одновременно (соединения с БД с некоторыми компонентами DBAPI, курсоры с другими и т. Д.) - это позволяет вам расслабиться требование выделенного потока в пользу большего количества пула (поток пула, который получает из общей очереди запрос, требующий ресурсов в очереди, получит этот ресурс из соответствующей очереди, ожидает при необходимости и т. д. и т. д.).
На самом деле Twisted - это хороший способ организовать этот менуэт (или квадратный танец в зависимости от обстоятельств) не только благодаря отложенным, но и из-за его надежной, надежной и масштабируемой базовой архитектуры: вы можете организовать вещи так, чтобы использовать потоки или подпроцессы только тогда, когда действительно гарантированно, но при этом большинство вещей считается поточно-ориентированным в одном потоке, управляемом событиями.
Но я понимаю, что Twisted не для всех - "выделять или объединять ресурсы, использовать очередь в wazoo, никогда не делать ничего, требующего блокировки или, запрет Гвидо, любая процедура синхронизации, даже более продвинутая, такая как семафор или условие", может все еще будет использоваться, даже если вы просто не можете обернуть голову вокруг асинхронных методологий, управляемых событиями, и по-прежнему обеспечите большую надежность и производительность, чем любой другой широко применяемый подход к многопоточности, с которым я когда-либо сталкивался.
Это зависит от того, что вы пытаетесь сделать, но я неравнодушен к использованию 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()
Kamaelia - это среда разработки Python для создания приложений с множеством взаимодействующих процессов.
Вот видео из Pycon 2009. Оно начинается со сравнения Kamaelia с Twisted и Parallel Python, а затем дает демонстрацию Kamaelia.http://www.kamaelia.org/cat-trans-medium.png Kamaelia - Параллелизм стал полезным, веселым
В Kamaelia вы строите системы из простых компонентов, которые взаимодействуют друг с другом. Это ускоряет разработку, значительно облегчает обслуживание, а также означает, что вы создаете естественно параллельное программное обеспечение. Он предназначен для доступа любому разработчику, включая новичков. Это также делает это забавным:)
Какие системы? Сетевые серверы, клиенты, настольные приложения, игры на основе Pygame, системы транскодирования и конвейеры, системы цифрового телевидения, средства искоренения спама, средства обучения и многое другое:)
Легкий параллелизм с Камаелией - Часть 1 (59:08)
Легкий параллелизм с Камаелией - Часть 2 (18:15)
Относительно Камаэлии, ответ выше не действительно покрывает выгоду здесь. Подход Kamaelia предоставляет унифицированный, прагматичный, не идеальный интерфейс для работы с потоками, генераторами и процессами в единой системе для параллелизма.
По сути, это метафора работающей вещи, которая имеет входящие и исходящие. Вы отправляете сообщения в почтовые ящики, а при соединении сообщения перенаправляются из почтовых ящиков в почтовые ящики. Эта метафора /API остается неизменной независимо от того, используете ли вы генераторы, потоки или процессы или общаетесь с другими системами.
"Не идеальная" часть происходит из-за того, что синтаксический сахар еще не добавлен для входящих и исходящих сообщений (хотя это обсуждается) - в системе сделан акцент на безопасность / удобство использования.
Если взять пример производителя-потребителя, использующий вышеизложенные потоки, то в Камаэлии это выглядит так:
Pipeline(Producer(), Consumer() )
В этом примере не имеет значения, являются ли они резьбовыми компонентами или нет, единственное различие между ними с точки зрения использования - базовый класс для компонента. Компоненты генератора взаимодействуют с использованием списков, резьбовых компонентов с использованием Queue.Queues и процессов на основе os.pipes.
Причина такого подхода заключается в том, чтобы усложнить устранение ошибок. При работе с потоками - или при любом параллельном использовании совместно используемой памяти проблема номер один, с которой вы сталкиваетесь, заключается в случайном нарушении обновлений общих данных. Используя передачу сообщений, вы устраняете один класс ошибок.
Если вы используете голые потоки и блокировки везде, вы обычно работаете с предположением, что когда вы пишете код, вы не допустите ошибок. Хотя мы все стремимся к этому, это случается очень редко. Обобщая поведение блокировки в одном месте, вы упрощаете, где что-то может пойти не так. (Обработчики контекста помогают, но не помогают со случайными обновлениями вне обработчика контекста)
Очевидно, что не каждый фрагмент кода может быть написан как передача сообщений и общий стиль, поэтому Kamaelia также имеет простую программную транзакционную память (STM), которая является действительно изящной идеей с неприятным именем - это больше похоже на управление версиями для переменных - т.е. проверить некоторые переменные, обновить их и зафиксировать обратно. Если вы столкнулись, вы промойте и повторите.
Соответствующие ссылки:
- Europython 09 учебник
- Ежемесячные выпуски
- Список рассылки
- Примеры
- Примеры приложений
- Многоразовые компоненты (генератор и нить)
Во всяком случае, я надеюсь, что это полезный ответ. FWIW, основная причина установки Kamaelia заключается в том, чтобы сделать параллелизм более безопасным и простым в использовании в системах Python, без хвоста, который виляет собакой. (т.е. большое ведро компонентов
Я могу понять, почему другой ответ Камаэлии был изменен, поскольку даже для меня это больше похоже на рекламу, чем на ответ. Как автору Kamaelia приятно видеть энтузиазм, хотя я надеюсь, что это содержит немного более релевантный контент:-)
И это мой способ сказать, пожалуйста, примите к сведению, что этот ответ по определению является предвзятым, но для меня цель Камаэлии состоит в том, чтобы попытаться обернуть то, что является лучшей практикой ИМО. Я бы посоветовал попробовать несколько систем и посмотреть, какие из них вам подходят. (также, если это не подходит для переполнения стека, извините - я новичок в этом форуме:-)
Я бы использовал Microthreads (Tasklets) из Stackless Python, если бы мне вообще пришлось использовать потоки.
Целая онлайн-игра (многопользовательская) построена вокруг Stackless и его принципа многопоточности - так как оригинал просто медленен для многопользовательской игры.
Темы в CPython широко не рекомендуется. Одной из причин является GIL - глобальная блокировка интерпретатора, которая сериализует многопоточность для многих частей выполнения. Мой опыт показывает, что так сложно создавать быстрые приложения. Мой пример кодирования, где все медленнее с многопоточностью - с одним ядром (но многие ожидания ввода должны были сделать возможным некоторое повышение производительности).
С CPython лучше использовать отдельные процессы, если это возможно.
Если вы действительно хотите испачкать руки, вы можете попробовать использовать генераторы для подделки сопрограмм. Возможно, он не самый эффективный с точки зрения выполняемой работы, но сопрограммы действительно предлагают вам очень хороший контроль над совместной многозадачностью, а не с упреждающей многозадачностью, которую вы найдете в другом месте.
Одно преимущество, которое вы обнаружите, заключается в том, что в целом вам не понадобятся блокировки или мьютексы при использовании совместной многозадачности, но для меня более важным преимуществом была почти нулевая скорость переключения между "потоками". Конечно, Stackless Python, как говорят, очень хорош для этого; и затем есть Erlang, если это не обязательно должен быть Python.
Вероятно, самым большим недостатком в совместной многозадачности является общее отсутствие обходного пути для блокировки ввода / вывода. И в поддельных сопрограммах вы также столкнетесь с проблемой, заключающейся в том, что вы не можете переключать "потоки" из чего-либо, кроме верхнего уровня стека в потоке.
После того, как вы сделали даже немного сложное приложение с поддельными сопрограммами, вы действительно начнете ценить работу, связанную с планированием процессов на уровне ОС.