2016-07-14 4 views
1

как вы?AsyncHTTPClient блокирует мой Tornado IOLoop

Я пережил эту проблему в последние дни, и мне кажется, что я не могу полностью понять библиотеку генома торнадо.

У меня есть этот кусок кода, в качестве примера:

@gen.coroutine 
def get(self, build_id=None): 
    status_query = self.get_query_arguments("status") 
    limit_query = self.get_query_arguments("limit") 

    results = [self._dummy() for i in range(15)] 
    yield results 

def _dummy(self): 
    http_client = tornado.httpclient.AsyncHTTPClient() 
    return http_client.fetch("https://www.google.com", headers=self.headers, validate_cert=False) 

Как я думал, мои 15 запросов на выборку Google должен быть запуск почти одновременно. Список «результаты» должен быть списком фьючерсов, а затем, приведя список, следует дождаться завершения всех этих операций.

Это на самом деле происходит, но для выполнения этих запросов требуется около 6 секунд, и оно увеличивается постепенно, поскольку я увеличиваю диапазон цикла for.

Разве они не должны в то же время быть готовыми?

Я что-то упустил?

спасибо!

+0

Если ваши запросы не связаны с IO, вы не увидите много изменений. –

+0

Не могли бы вы объяснить мне немного больше? :) –

ответ

2

Максимальное значение max_clients по умолчанию AsyncHTTPClient равно 10. Когда вы инициируете 15 запросов, 10 из них начинаются немедленно, а оставшиеся 5 должны дождаться завершения других запросов до их начала. Чтобы начать более параллельные запросы, увеличьте max_clients до большего числа. See Tornado's documentation for details on configuring AsyncHTTPClient.

+0

Но, как это в моем примере, он будет создавать разные экземпляры AsyncHTTPClient, каждый из которых делает только один запрос ... Я не прав? –

+0

Да - внутренне, объекты AsyncHTTPClient обмениваются одной очередью запросов. –

+0

Да, я только что нашел это в документах. Замечательно. Buuuut, я только что увеличил max_clients до 50 и все еще имею ту же проблему. Время увеличивается по мере увеличения количества запросов. (Без превышения 50) –

1

Если ваши запросы не связаны с IO, вы не увидите много изменений. - Me

В программировании это основные ограничения, которые мы имеем:

  • CPU (количество вычислений, которые могут произойти в секунду)
  • доступа кэша в процессоре
  • доступа ОЗУ
  • Доступ к дискам
  • Доступ к сети

В Python мы еще больше ограничены, когда речь идет о доступе к ЦП из-за GIL. С современными компьютерами, которые имеют тенденцию к нескольким ядрам - 2, 4, 8 или 16 - мы калекой еще больше, потому что обычно каждый из этих процессоров будет немного медленнее. Для получения дополнительной информации о GIL, проверьте David Beazley's GIL talk и Larry Hasting's GIL-ectomy.

Чтобы обойти глобальную блокировку интерпретатора, было разработано несколько модулей обратного вызова, таких как Twisted, Tornado и asyncio. Способ, которым они работают, заключается в выполнении некоторых операций, обычно приводящих к управлению, когда они достигают точки, где останавливается IO.

Например, если я пишу данные на вращающийся диск, возможно, я могу записать 100 кб на диск, но пока я жду, когда вся эта информация будет написана, возможно, я смогу уйти и сделать 1000 расчеты до того, как все данные завершат запись.

В качестве альтернативы, возможно, я могу сделать 100 запросов в секунду для веб-службы, но мне требуется 0,0001s для выполнения моих расчетов для каждого из этих запросов. Если посмотреть на график, где я провожу время это будет выглядеть примерно так:

#    
    #    
    #    
    #    
    #    
    #    
    #    
    #   # 
-------------------------- 
reading processing 

То, что эти процессы позволяют сделать чередовать обработку и чтение/запись, отправив запрос пакетов от , затем делать что-то еще, а затем в какой-то момент вернуться к чтению возвращенных пакетов.

Существо IO связаны, как это, вы можете увидеть довольно массивное ускорение, потому что вместо того, чтобы искать что-то вроде этого:

start end start  end 
--|--------|----|--------|------ 
t=0  t=5 t=6  t=11 

Вы можете получить что-то вроде этого:

 start  end 
start|  end | 
--|---|------|---|- 
t=0 t=1 t=5 t=6 

Но если ваш процесс связан с ЦП, вы не увидите какого-либо из этих ускорений (или, по крайней мере, не так много), потому что вы тратите 30 секунд на обработку, и только 1s делает ожидания в сети.

Перед тем как попробовать асинхронный подход, дайте стандартный однопоточный подход и посмотрите 1) если он достаточно быстрый и 2) если он медленный на границе сети/IO.

Вы можете легко использовать что-то вроде line profiler для Python, и (если еще не) выделить функции чтения, обработки и записи и посмотреть, где вы проводите время. Если вы тратите большую часть времени на функции чтения, тогда да, вы должны увидеть довольно разумное ускорение от асинхронного подхода. Если нет, async просто замедлит вас.

Честно говоря, это не так уж плохо, если у вас есть что-то супер скорость критической. И тогда вы должны использовать cffi или что-то, чтобы взять критические разделы скорости и свалить их на C. Вы сделали выяснить, какие разделы являются задержкой, не так ли?

+0

следует принять ответ – desertkun