РЕДАКТИРОВАТЬ : Я расширяю ответ, чтобы включить более отточенный пример. В этом посте я обнаружил много враждебности и дезинформации относительно потоков. асинхронный ввод / вывод. Поэтому я также добавил дополнительные аргументы, чтобы опровергнуть некоторые недействительные претензии Я надеюсь, что это поможет людям выбрать правильный инструмент для правильной работы.
Это ответ на вопрос 3 дня назад.
Python urllib2.open работает медленно, нужен лучший способ прочитать несколько URL-адресов - переполнение стека
Python urllib2.urlopen () медленный, нужен лучший способ прочитать несколько URL
Я полирую код, чтобы показать, как получать несколько веб-страниц параллельно, используя потоки.
import time
import threading
import Queue
# utility - spawn a thread to execute target for each args
def run_parallel_in_threads(target, args_list):
result = Queue.Queue()
# wrapper to collect return value in a Queue
def task_wrapper(*args):
result.put(target(*args))
threads = [threading.Thread(target=task_wrapper, args=args) for args in args_list]
for t in threads:
t.start()
for t in threads:
t.join()
return result
def dummy_task(n):
for i in xrange(n):
time.sleep(0.1)
return n
# below is the application code
urls = [
('http://www.google.com/',),
('http://www.lycos.com/',),
('http://www.bing.com/',),
('http://www.altavista.com/',),
('http://achewood.com/',),
]
def fetch(url):
return urllib2.urlopen(url).read()
run_parallel_in_threads(fetch, urls)
Как видите, код приложения имеет всего 3 строки, которые можно свернуть в 1 строку, если вы агрессивны. Я не думаю, что кто-то может оправдать их утверждение, что это сложно и не поддается ремонту.
К сожалению, большинство других потоковых кодов, размещенных здесь, имеют некоторые недостатки. Многие из них проводят активный опрос, чтобы дождаться завершения кода. join()
- лучший способ синхронизации кода. Я думаю, что этот код улучшил все примеры потоков.
поддержание связи
Предложение WoLpH об использовании keep-alive соединения может быть очень полезным, если все ваши URL-адреса указывают на один и тот же сервер.
витой
Аарон Галлахер - фанат фреймворка twisted
и он враждебен ко всем, кто предлагает нить. К сожалению, многие его заявления являются дезинформацией. Например, он сказал «-1 для предложения потоков. Это связано с IO; потоки здесь бесполезны». Это противоречит доказательствам, поскольку и Ник Т, и я продемонстрировали увеличение скорости от использования потока. На самом деле приложение, связанное с вводом / выводом, имеет наибольшую выгоду от использования потока Python (v.s. нет выигрыша в приложении, связанном с процессором). Неправильная критика Аарона в отношении потоков показывает, что он довольно смущен параллельным программированием в целом.
Правильный инструмент для правильной работы
Мне хорошо известны проблемы, связанные с параллельным программированием с использованием потоков, Python, асинхронного ввода-вывода и так далее. У каждого инструмента есть свои плюсы и минусы. Для каждой ситуации есть соответствующий инструмент. Я не против крученого (хотя сам не развернул). Но я не верю, что мы можем сказать, что нить ПЛОХАЯ, а скрутка ХОРОША во всех ситуациях.
Например, если требование OP заключается в параллельном извлечении 10 000 веб-сайтов, предпочтительным будет асинхронный ввод-вывод. Потоки не будут подходящими (если не возможно с Python без стеков).
Противостояние Аарона нитям - это в основном обобщения. Он не понимает, что это тривиальная задача распараллеливания. Каждая задача независима и не разделяет ресурсы. Так что большинство его атак не применимо.
Поскольку мой код не имеет внешней зависимости, я назову его подходящим инструментом для правильной работы.
Производительность
Я думаю, что большинство людей согласится с тем, что производительность этой задачи во многом зависит от сетевого кода и внешнего сервера, где производительность кода платформы должна иметь незначительное влияние. Однако тест Аарона показывает увеличение скорости на 50% по сравнению с многопоточным кодом. Я думаю, что необходимо реагировать на это очевидное увеличение скорости.
В коде Ника есть очевидный недостаток, вызвавший неэффективность. Но как вы объясните увеличение скорости на 233 мс по сравнению с моим кодом? Я думаю, что даже фанаты скручивания будут воздерживаться от поспешных выводов, чтобы приписать это эффективности скрученного. В конце концов, существует огромный объем переменных вне системного кода, таких как производительность удаленного сервера, сеть, кэширование и реализация различий между urllib2 и витым веб-клиентом и т. Д.
Чтобы убедиться, что потоки Python не повлекут за собой огромную неэффективность, я делаю быстрый тест для создания 5 потоков, а затем 500 потоков.Я вполне могу сказать, что издержки порождения 5-го потока незначительны и не могут объяснить разницу в скорости 233 мс.
In [274]: %time run_parallel_in_threads(dummy_task, [(0,)]*5)
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
Wall time: 0.00 s
Out[275]: <Queue.Queue instance at 0x038B2878>
In [276]: %time run_parallel_in_threads(dummy_task, [(0,)]*500)
CPU times: user 0.16 s, sys: 0.00 s, total: 0.16 s
Wall time: 0.16 s
In [278]: %time run_parallel_in_threads(dummy_task, [(10,)]*500)
CPU times: user 1.13 s, sys: 0.00 s, total: 1.13 s
Wall time: 1.13 s <<<<<<<< This means 0.13s of overhead
Дальнейшее тестирование моей параллельной выборки показывает огромную изменчивость времени отклика в 17 запусках.(К сожалению, я не проверял код Аарона).
0.75 s
0.38 s
0.59 s
0.38 s
0.62 s
1.50 s
0.49 s
0.36 s
0.95 s
0.43 s
0.61 s
0.81 s
0.46 s
1.21 s
2.87 s
1.04 s
1.72 s
Мое тестирование не подтверждает вывод Аарона о том, что многопоточность постоянно медленнее асинхронного ввода-вывода с измеримым запасом.Учитывая количество задействованных переменных, я должен сказать, что это недопустимый тест для измерения систематической разницы в производительности между асинхронным вводом-выводом и многопоточностью.