Как OkHttp выполняет параллельные HTTP-запросы с кажущимися синхронными HTTP-соединениями без использования потоков?

Я провел некоторое тестирование производительности с помощью библиотеки OkHttp и нашел, что это здорово. Он выполнил 80 запросов http://httpbin.org/delay/1, который намеренно приостанавливает 1 секунду для каждого запроса, в 4.7s на моем телефоне HTC One. Я просмотрел код, и я пытаюсь выяснить, почему он так быстро. Разработчики (Square Inc) рекламируют пулы соединений и асинхронные звонки, которые, как я считаю, способствуют хорошей производительности.

Я пришел из мира .NET, а в .NET 4.5 у вас есть истинная асинхронная HTTP-библиотека с методом Async GetResponse. Придавая поток ОС в ожидании ответа, вы освобождаете ресурсы, чтобы инициировать больше HTTP-запросов или других вещей. Дело в том, что я не могу видеть тот же шаблон с OkHttp (или любой другой библиотекой HTTP для Android, на которую я смотрел). Итак, как я могу выполнить 80 запросов на 1 секунду за 4 секунды? Это не основано на потоках, не так ли? Я не запускаю 80 (или 20) потоков?

Чтобы быть конкретным, в com.squareup.okhttp.Call.beginRequest(), я не вижу выхода нити между sendRequest и getResponse:

if (canceled) return null;

try {
    engine.sendRequest();

    if (request.body() != null) {
        BufferedSink sink = engine.getBufferedRequestBody();
        request.body().writeTo(sink);
    }

    engine.readResponse();
} catch (IOException e) {
    HttpEngine retryEngine = engine.recover(e, null);
    if (retryEngine != null) {
        engine = retryEngine;
        continue;
    }

    // Give up; recovery is not possible.
    throw e;
}

Response response = engine.getResponse();

Итак, как сделать 80 "параллельных" звонков возможным?

Мне не нужно это знать, чтобы использовать библиотеку, но меня интересует асинхронное программирование, и мне очень хотелось бы понять, как это разрешило OkHttp/SquareInc.

Ответ 1

Я сам тестировал себя, связав источник OkHttp с моим проектом и введя запись в основной класс запросов - Call.java. Я обнаружил, что OkHttp действительно использует поток для каждого вызова и не дает ответа в ожидании ответа, как я ошибочно предполагал. Единственная причина, по которой он быстрее, чем, например, Volley, заключается в том, что Volley имеет жестко заданный предел потока в 4, а OkHttp использует Integer.MAX_VALUE (Dipatcher.java строка 58):

executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
      new LinkedBlockingQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));

Вот выдержка журнала LogCat, когда я поставил в очередь и исключил 80 запросов "асинхронно":

05-31 12:15:23.884  27989-28025/nilzor.okhttp I/OKH﹕ Starting request 1
05-31 12:15:23.884  27989-28026/nilzor.okhttp I/OKH﹕ Starting request 2
05-31 12:15:24.044  27989-28199/nilzor.okhttp I/OKH﹕ Starting request 79
05-31 12:15:24.054  27989-28202/nilzor.okhttp I/OKH﹕ Starting request 80
05-31 12:15:25.324  27989-28025/nilzor.okhttp I/OKH﹕ Getting response 1 after 1436ms
05-31 12:15:26.374  27989-28026/nilzor.okhttp I/OKH﹕ Getting response 2 after 2451ms
05-31 12:15:27.334  27989-28199/nilzor.okhttp I/OKH﹕ Getting response 79 after 3289ms
05-31 12:15:26.354  27989-28202/nilzor.okhttp I/OKH﹕ Getting response 80 after 2305ms

Третий столбец в формате xxxxx-yyyyy указывает идентификатор процесса (x) и идентификатор потока (y). Обратите внимание, как каждый запрос получает свой собственный поток и как тот же поток обрабатывает ответ. Полный журнал. Это означает, что у нас есть 80 блокирующих потоков во время ожидания ответов, что не так, как должно выполняться асинхронное программирование.

В защите OkHttp/Square Inc они никогда не утверждают, что имеют истинную сквозную асинхронную HTTP-связь, они предоставляют пользователю только асинхронный интерфейс. Это нормально. И он также хорошо работает и делает кучу других вещей. Это хорошая библиотека, но я неверно истолковал ее за истинную асинхронную HTTP-связь.

С тех пор я понял, что нужно искать ключевые слова "NIO", чтобы найти то, что я ищу. Библиотеки, такие как AndroidAsync и Ion, кажутся многообещающими.

Ответ 2

В настоящее время OkHttp не использует асинхронные сокеты. Если вы используете асинхронный API с enqueue(), Dispatcher будет вращать несколько потоков и выполнять несколько одновременных запросов. Если вы используете один и тот же клиент OkHttp для всех запросов, он ограничится 5 подключениями на хост.