time

Параллелизм в Python

python_mt.png В последние время сталкивался с несколькими задачами, когда выполнение задачи в один поток не самое оптимальное решение. Например, при обработки какой либо тяжелой задачи в gui-приложении может медленно обновляться интерфейс приложения, тогда полезно запустить дополнительный поток для выполняемой задачи, а gtk-приложению оставить его родной поток, который будет отвечать за прорисовку, например, прогрессбара. Еще одной задачкой был парсинг пачки xml-файлов и занесение результата в БД - удобно запустить несколько процессов, каждый из которых будет работать с отдельно взятым файлов.

Для работы с параллелизм в python есть два встроенных модуля: multiprocessing (начиная с 2.6) и threading.

Коротко про состояние дел на текущий момент. В python потоки реализованы через POSIX Threads (т.н. native thread). Также стоит упомянуть про GIL (Global Interpreter Lock) в threading. Исторически так сложилось, что большинство структур данных (списки, словари и т.д.), которые находятся в глобальном пространстве интерпретатора, не потоко-безопасны. Потому разработчики решили ограничить количество одновременно работающих потоков до одного. Т.е. в одно и тоже время может работать только один поток, который захватил маркер GIL (в реализации на С это выглядит как выставление лока на указатель _PyTheadState_Current). Каждый 100 'тиков' интерпретатор освобождает лок и дает возможность выполнится другому потоку.

Таким образом, в текущей реализации многопоточности в python мы можем выполнять только один поток в одно и тоже время и только на одном cpu. К сожалению в C-шной реализации python 3 все останется без изменений.

Подробнее про GIL:

Но не все так печально, как может показаться на первый взгляд :). Существует масса готовых решений, со своими плюсами и минусами:

UPDATE: Я встречал в инете разные кусочки такой шарады как параллелизм (в данном контексте, я понимаю threading, multiprocessing, tasklets, reactor / event-based models, coroutine) в python, но нигде не было комплексного обзора, с упоминанием возможных вариантов распараллеливания задач в питоне. Целью этого поста было сделать попытку хоть как-то систематизировать существующие варианты (по крайней мере у меня в голове :)).

Коллективный разум (к сожалению пока только в лице одного человека из комментариев, Max'а) внес коррективы в первоначальный список решений:

Подходят:

  • использовать возможности модуля multiprocessing;
  • использовать newthreading, пока еще молодой концепт, но есть заявление о том что хотят избавится от GIL;
  • Parallel Python
  • python-safethread
  • использовать не C-шную реализации python, например Jython или IronPython

Частично подходят (GIL detected):

  • использовать возможности модуля threading
  • использовать асинхронный подход. Хороший список вариантов есть на stackoverflow.com. Еще одна обзорная статья Asynchronous Servers in Python.
  • использовать Stackless Python. В Stackless решили использовать легковесные потоки (в сравнение со стандартными питоновскими), но GIL по прежнему присутствует, пруф-линк. Сравнение скорости потоков из threading и Stackless.
Just Say No to the combined evils of locking, deadlocks, lock granularity, livelocks, nondeterminism and race conditions.

высказывание Гвидо ван Россума о будущем GIL.

А теперь перейдем от теории к практики, попробуем замерить скорость вычисления какой-либо математической задачки и операций I/O. Будем замерять время выполнения без какого-либо параллельного выполнения и с помощью модулей multiprocessing и threading.

В качестве математической задачки выбрал подсчет факториала для чисел от 200 до 300. Для I/O операций буду пинговать 8 разных сайтов. Почему именно эти тесты? Мне так захотелось! :) Есть конструктивные предложения? Буду рад выслушать в комментах.

Замеры проводятся на Ubuntu 10.04, AMD Athlon(tm) 64 X2 Dual Core Processor 5600+, 2 GB RAM. Для обработки создавались 4 потока для threading и 4 процесса для multiprocessing. Тест запускался 3 раза, бралось средние значение (в секундах), для многопоточного и многопроцессорного выполнения также приплюсовывалось время на заполнение очереди.

Что бы минимизировать затраты на создание/удаление потоков и процессов было создано ограниченное количество потоков/процессов (пул). Освободившийся поток/процесс сам выбирали следующие данные из очереди.

Таск Последовательное выполнение threading   multiprocessing Сорцы
Факториал чисел от 200 до 300 0.026 0.039 0.020 factorial_smt.zip
Пинг 8 разных сайтов 41.33 15.17 15.18 ping_smt.zip

Дополнительное чтиво

blog comments powered by Disqus