В последние время сталкивался с несколькими задачами, когда выполнение задачи в один поток не самое оптимальное решение. Например, при обработки какой либо тяжелой задачи в 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'а) внес коррективы в первоначальный список решений:
Подходят:
Частично подходят (GIL detected):
высказывание Гвидо ван Россума о будущем 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 |
Дополнительное чтиво