Я открываю файл, который имеет 100 000 URL. Я должен отправить Запрос HTTP в каждый URL и распечатать код состояния. Я использую Python 2.6 и до сих пор посмотрел на многие запутывающие способы, которыми Python реализует поточную обработку/параллелизм. Я даже посмотрел на библиотеку согласия Python, но не могу выяснить, как записать эту программу правильно. Кто-либо столкнулся с подобной проблемой? Я обычно предполагаю, что я должен знать, как выполнить тысячи задач в Python максимально быстро - я предполагаю, что это означает 'одновременно'.
Решение без витой схемы:
from urlparse import urlparse
from threading import Thread
import httplib, sys
from Queue import Queue
concurrent = 200
def doWork():
while True:
url = q.get()
status, url = getStatus(url)
doSomethingWithResult(status, url)
q.task_done()
def getStatus(ourl):
try:
url = urlparse(ourl)
conn = httplib.HTTPConnection(url.netloc)
conn.request("HEAD", url.path)
res = conn.getresponse()
return res.status, ourl
except:
return "error", ourl
def doSomethingWithResult(status, url):
print status, url
q = Queue(concurrent * 2)
for i in range(concurrent):
t = Thread(target=doWork)
t.daemon = True
t.start()
try:
for url in open('urllist.txt'):
q.put(url.strip())
q.join()
except KeyboardInterrupt:
sys.exit(1)
Это решение немного быстрее, чем решение с витой схемой, и использует меньше ЦП.
Использование пула потоков - хороший вариант, который упростит эту задачу. К сожалению, у python нет стандартной библиотеки, которая упрощает создание пулов потоков. Но вот достойная библиотека, с которой вы должны начать: http://www.chrisarndt.de/projects/threadpool/
Пример кода с их сайта:
pool = ThreadPool(poolsize)
requests = makeRequests(some_callable, list_of_args, callback)
[pool.putRequest(req) for req in requests]
pool.wait()
Надеюсь, это поможет.
В вашем случае многопоточность, вероятно, поможет, так как вы, вероятно, будете проводить большую часть времени в ожидании ответа. В стандартной библиотеке есть полезные модули, такие как Queue , которые могут помочь.
Раньше я делал то же самое с параллельной загрузкой файлов, и для меня это было достаточно хорошо, но не в том масштабе, о котором вы говорите.
Если ваша задача была больше связана с процессором, вы можете посмотреть на модуль multiprocessing , который позволит вам использовать больше процессоров / ядер / потоков (больше процессов, которые не будут блокировать друг друга поскольку блокировка выполняется для каждого процесса)
Хороший подход к решению этой проблемы - сначала написать код, необходимый для получения одного результата, а затем включить код потоковой передачи для распараллеливания приложения.
В идеальном мире это просто означало бы одновременный запуск 100000 потоков, которые выводят свои результаты в словарь или список для последующей обработки, но на практике вы ограничены тем, сколько параллельных HTTP-запросов вы можете отправить таким образом. Локально у вас есть ограничения на количество сокетов, которые вы можете открывать одновременно, сколько потоков выполнения позволит ваш интерпретатор Python. Удаленно вы можете быть ограничены в количестве одновременных подключений, если все запросы относятся к одному серверу или нескольким. Эти ограничения, вероятно, потребуют, чтобы вы написали сценарий таким образом, чтобы в любой момент опрашивать только небольшую часть URL-адресов (100, как упомянул другой плакат, вероятно, является приличным размером пула потоков, хотя вы можете обнаружить, что можно успешно развернуть еще много).
Вы можете следовать этому шаблону проектирования, чтобы решить указанную выше проблему:
или dict
в CPython, вы можете безопасно добавлять или вставлять уникальные элементы из ваших потоков без блокировок , но если вы выполняете запись в файл или требуете более сложного взаимодействия между потоками данных , вы должны использовать блокировку взаимного исключения для защиты этого состояния от повреждения . Я бы посоветовал вам использовать модуль threading . Вы можете использовать его для запуска и отслеживания запущенных потоков. Поддержка потоковой передачи Python ограничена, но описание вашей проблемы предполагает, что этого вполне достаточно для ваших нужд.
Наконец, если вы хотите увидеть довольно простое приложение параллельного сетевого приложения, написанное на Python, посмотрите ssh.py . Это небольшая библиотека, которая использует потоки Python для распараллеливания многих соединений SSH. Дизайн достаточно близок к вашим требованиям, поэтому вы можете счесть его хорошим ресурсом.
Если вы хотите добиться максимальной производительности, вы можете рассмотреть возможность использования асинхронного ввода-вывода, а не потоков. Накладные расходы, связанные с тысячами потоков ОС, нетривиальны, а переключение контекста в интерпретаторе Python добавляет еще больше. Многопоточность, безусловно, выполнит свою работу, но я подозреваю, что асинхронный маршрут обеспечит лучшую общую производительность.
В частности, я бы предложил асинхронный веб-клиент в библиотеке Twisted ( http://www.twistedmatrix.com ). У него, по общему признанию, крутая кривая обучения, но его довольно легко использовать, если вы хорошо разбираетесь в стиле асинхронного программирования Twisted.
Практическое руководство по API асинхронного веб-клиента Twisted доступно по адресу:
http://twistedmatrix.com/documents/current/web/howto/client.html
Решение:
from twisted.internet import reactor, threads
from urlparse import urlparse
import httplib
import itertools
concurrent = 200
finished=itertools.count(1)
reactor.suggestThreadPoolSize(concurrent)
def getStatus(ourl):
url = urlparse(ourl)
conn = httplib.HTTPConnection(url.netloc)
conn.request("HEAD", url.path)
res = conn.getresponse()
return res.status
def processResponse(response,url):
print response, url
processedOne()
def processError(error,url):
print "error", url#, error
processedOne()
def processedOne():
if finished.next()==added:
reactor.stop()
def addTask(url):
req = threads.deferToThread(getStatus, url)
req.addCallback(processResponse, url)
req.addErrback(processError, url)
added=0
for url in open('urllist.txt'):
added+=1
addTask(url.strip())
try:
reactor.run()
except KeyboardInterrupt:
reactor.stop()
Testtime:
[kalmi@ubi1:~] wc -l urllist.txt
10000 urllist.txt
[kalmi@ubi1:~] time python f.py > /dev/null
real 1m10.682s
user 0m16.020s
sys 0m10.330s
[kalmi@ubi1:~] head -n 6 urllist.txt
http://www.google.com
http://www.bix.hu
http://www.godaddy.com
http://www.google.com
http://www.bix.hu
http://www.godaddy.com
[kalmi@ubi1:~] python f.py | head -n 6
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
Pingtime:
bix.hu is ~10 ms away from me
godaddy.com: ~170 ms
google.com: ~30 ms
Рассмотрите возможность использования Windmill , хотя Windmill, вероятно, не может выполнять такое количество потоков.
Вы можете сделать это с помощью скрученного вручную скрипта Python на 5 машинах, каждая из которых подключается к исходящему каналу через порты 40000-60000, открывая 100 000 портовых соединений.
Кроме того, было бы полезно провести образец теста с помощью приложения QA с хорошей многопоточностью, такого как OpenSTA , чтобы получить представление о том, сколько может обрабатывать каждый сервер.
Также попробуйте изучить простой Perl с классом LWP :: ConnCache. Так вы, вероятно, получите больше производительности (больше соединений).
Обсуждения здесь не подходят. Они обеспечат узкие места как процесса, так и ядра, а также ограничения пропускной способности, которые неприемлемы, если общая цель - «самый быстрый способ».
Немного скрученного
и его асинхронного HTTP
клиента даст вам гораздо лучшие результаты.
Самый простой способ - использовать встроенную библиотеку потоков Python. Это не «настоящие» потоки / потоки ядра У них есть проблемы (например, сериализация), но они достаточно хороши. Вам нужен пул очереди и потоков. Один из вариантов - здесь , но написать свой собственный тривиально. Вы не можете распараллелить все 100 000 вызовов, но можете запустить 100 (или около того) из них одновременно.