Многопроцессорность бесполезна с urllib2? - PullRequest
9 голосов
/ 02 августа 2011

Недавно я попытался ускорить небольшой инструмент (который использует urllib2 для отправки запроса (неофициальный) twitter-button-count-url (> 2000 URL-адресов) и анализирует его результаты) с помощью модуля многопроцессорной обработки (и это рабочие бассейны). Я прочитал несколько обсуждений здесь о многопоточности (которая замедлила все это по сравнению со стандартной, не поточной версией) и многопроцессорности, но я не смог найти ответ на (возможно, очень простой) вопрос:

Можете ли вы ускорить url-вызовы с помощью многопроцессорной обработки или нет узкого места, например, сетевого адаптера? Я не вижу, например, какую часть метода urllib2-open можно распараллелить и как это должно работать ...

РЕДАКТИРОВАТЬ: Это запрос, который я хочу ускорить, и текущая многопроцессорная установка:

 urls=["www.foo.bar", "www.bar.foo",...]
 tw_url='http://urls.api.twitter.com/1/urls/count.json?url=%s'

 def getTweets(self,urls):
    for i in urls:
        try:
            self.tw_que=urllib2.urlopen(tw_url %(i))
            self.jsons=json.loads(self.tw_que.read())
            self.tweets.append({'url':i,'date':today,'tweets':self.jsons['count']})
        except ValueError:
            print ....
            continue
    return self.tweets 

 if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)            
    result = [pool.apply_async(getTweets(i,)) for i in urls]
    [i.get() for i in result]

Ответы [ 5 ]

7 голосов
/ 02 августа 2011

А вот и еще одна дискуссия о GIL. Ну, вот в чем дело. Выборка контента с urllib2 будет в основном IO-привязана. Собственные потоки и многопроцессорность будут иметь одинаковую производительность, когда задача связана с вводом-выводом (многопоточность становится проблемой только тогда, когда она связана с процессором). Да, вы можете ускорить это, я сделал это сам, используя потоки Python и что-то вроде 10 потоков загрузчика.

В основном вы используете модель производитель-потребитель с одним потоком (или процессом), создающим URL-адреса для загрузки, и N потоков (или процессов), потребляющих из этой очереди и выполняющих запросы к серверу.

Вот некоторый псевдокод:

# Make sure that the queue is thread-safe!!

def producer(self):
    # Only need one producer, although you could have multiple
    with fh = open('urllist.txt', 'r'):
        for line in fh:
            self.queue.enqueue(line.strip())

def consumer(self):
    # Fire up N of these babies for some speed
    while True:
        url = self.queue.dequeue()
        dh = urllib2.urlopen(url)
        with fh = open('/dev/null', 'w'): # gotta put it somewhere
            fh.write(dh.read())

Теперь, если вы загружаете очень большие порции данных (сотни МБ) и один запрос полностью насыщает полосу пропускания, тогда да, запуск нескольких загрузок не имеет смысла. Причина, по которой вы запускаете несколько загрузок (как правило), заключается в том, что запросы небольшие и имеют относительно высокую задержку / накладные расходы.

5 голосов
/ 02 августа 2011

Взгляните на gevent и, в частности, на этот пример: concurrent_download.py . Это будет значительно быстрее, чем многопроцессорная и многопоточность + он может легко обрабатывать тысячи соединений.

3 голосов
/ 02 августа 2011

Это зависит! Обращаетесь ли вы к разным серверам, являются ли передаваемые файлы маленькими или большими, теряете ли вы большую часть времени в ожидании ответа сервера или передачи данных, ...

Как правило, многопроцессорная обработка связана с некоторыми накладными расходами, и поэтому вы должны быть уверены, что ускорение, полученное при распараллеливании работы, больше, чем сами накладные расходы.

Другой момент: сетевые и, следовательно, связанные с вводом / выводом приложения работают - и масштабируются - лучше с асинхронным вводом / выводом и архитектурой, управляемой событиями, а не с многопоточностью или многопроцессорностью, поскольку в таких приложениях большая часть времени тратится на ожидание ввода / вывода. O и не делать никаких вычислений.

Для вашей конкретной проблемы я бы попытался реализовать решение, используя Twisted , gevent , Tornado или любую другую сетевую среду, в которой не используются потоки. распараллелить соединения.

1 голос
/ 02 августа 2011

То, что вы делаете, когда разделяете веб-запросы на несколько процессов, это распараллеливает сетевые задержки (то есть ожидание ответов). Так что обычно вы должны получить хорошее ускорение, так как большинство процессов большую часть времени должны спать, ожидая события.

Или используйте Twisted. ;)

0 голосов
/ 11 декабря 2015

Ничего не полезно, если ваш код не работает: f() (с круглыми скобками) немедленно вызывает функцию в Python, вы должны просто передать f (без круглых скобок) для выполнения в пуле.Ваш код из вопроса:

#XXX BROKEN, DO NOT USE
result = [pool.apply_async(getTweets(i,)) for i in urls]
[i.get() for i in result]

обратите внимание на круглые скобки после getTweets, что означает, что весь код выполняется в основном потоке последовательно.

Вместо этого делегируйте вызов пулу:

all_tweets = pool.map(getTweets, urls)

Кроме того, вам здесь не нужны отдельные процессы, если только json.loads() не является дорогим (с точки зрения использования процессора) в вашем случае.Вы можете использовать темы: замените multiprocessing.Pool на multiprocessing.pool.ThreadPool - остальное идентично.GIL высвобождается во время ввода-вывода в CPython, и поэтому потоки должны ускорить ваш код, если большая часть времени тратится на urlopen().read().

Вот пример полного кода .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...