Как Java NIO работает внутренне, используется ли внутренний пул потоков? - PullRequest
0 голосов
/ 01 февраля 2019

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

Есть ли у jvm пул потоков, где фактически выполняется синхронный ввод-вывод?Существует встроенная поддержка AIO для Linux - Java использует ее для внутреннего использования.Как AIO работает на уровне ОС - есть ли у него пул потоков, но на уровне ОС - или есть какая-то магия, что потоки вообще не нужны?

В общем, вопрос в том, дает ли асинхронный NIO намвозможность получить набор потоков - или это просто оболочка для синхронизированного ввода-вывода, которая позволяет нам иметь фиксированное количество потоков для выполнения ввода-вывода

Ответы [ 2 ]

0 голосов
/ 01 февраля 2019

Само ядро ​​(будь то windows, linux или что-то более экзотическое) отвечает за неблокирующий ввод / вывод, а java-классы в пакете nio (такие как Channel и Selector) являются просто довольно низкоуровневыми переводами.этого API.

Материал низкого уровня требует, чтобы вы сделали потоки, чтобы сделать это правильно.Базовая поддержка NIO в самой java. * Позволяет вам вызывать метод, который блокирует, пока, по крайней мере, одна вещь, в которой вы заинтересованы, не произойдет с любым количеством пакетных неблокирующих каналов.Вы можете, например, иметь 1000 открытых каналов, представляющих сетевые сокеты, все ожидающие «Мне интересно, поступят ли какие-либо сетевые пакеты на любое из этих 1000 открытых сокетов», а затем вызвать метод, чтобы сказать: «Пожалуйста, спите, пока что-нибудь интересное не случится»,Если вы настроили свое приложение для вызова этого метода, затем обработали все интересные вещи и вернулись к вызову этого метода, вы написали довольно неэффективное приложение: процессоры обычно имеют более одного ядра, и все, кроме одного, спятабсолютно ничего не делать.правильная модель состоит в том, чтобы несколько потоков (более или менее по одному на ядро) работали с одной и той же моделью «разбуди меня со списком интересных вещей».Вы не сможете избавиться от потоков, если не сделаете преднамеренно плохо работающий код.

Итак, давайте предположим, что вы правильно настроили его: у вас 8-ядерный процессор и 8 потоков, на которых выполняетсяЦикл 'wait-for-интересных вещей, handle-sockets-with-active-data'.

Представьте часть ваших кодовых блоков handle-sockets.То есть он делает что-то, что заставляет ЦП проверять выполнение других заданий, потому что ему приходится ждать, скажем, сети, диска или чего-то подобного.Скажем, потому что вы поместили туда несколько запросов к базе данных и не поняли, что запросы к БД используют (возможно, локальные, но все же) сетевые соединения и попадают на диск.Это было бы очень плохо: у вас достаточно ресурсов ЦП для обработки этих 1000 входящих запросов, но весь ваш набор из 8 потоков все ждет, пока БД выполнит свою работу, и хотя ЦП может анализировать пакеты и ответы, он получаетничего не остается делать и останавливается, ожидая, сколько лет потребуется БД для извлечения записи с диска.

Плохо.Итак, НЕ код блокировки вызова.К сожалению, в java есть тонны методов (как в библиотеках java core, так и в сторонних библиотеках), которые блокируют.Они, как правило, не документированы.Реального решения этой проблемы не существует.

Некоторые библиотеки предлагают решения, но если они это делают, они должны быть в форме «обратного вызова»: возьмите пример запроса к БД: вам нужно взятьэтот сетевой сокет, скажите ему, что вы, по крайней мере, на данный момент, больше не заинтересованы во входящих данных (вы уже ждете ответа БД, нет смысла пытаться обработать больше входящих данных для этого сокета);вместо этого вы хотите связать (и API-интерфейс NIO не поддерживает это сам, вам придется создать какую-то платформу) самого соединения с БД как «Мне интересно, готов ли этот запрос БД к ответу».Java как язык не подходит для написания таким образом, в результате вы получаете «ад обратного вызова», как работает javascript.Существуют решения для обратного вызова ада, но он остается сложным, и Java в основном не поддерживает их (например, «yield» - это то, что может помочь. Java не поддерживает концепцию выхода).

Наконец, естьэто производительность: ПОЧЕМУ вы хотите избавиться от потоков?

Потоки несут 2 основных штрафа:

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

  2. Стек.Как почти в каждой модели программирования, есть немного памяти, называемой «стеком», которая содержит локальные переменные и местоположение вызывающего вас метода (и вызывающего его метода, вплоть до вашего основного метода / выполнения потока).метод).Если вы получаете трассировку стека, вы смотрите на ее эффект.В Java каждый поток получает 1 стек, и все стеки имеют одинаковый размер.Вы можете настроить его с помощью аргумента -Xss JVM, а минимальное значение составляет 1 МБ.Это означает, что если вы хотите одновременно 4000 потоков, то это 4 ГБ стека, и этого нельзя избежать (и тогда вам понадобится больше памяти для кучи и тому подобного).

Но,неблокирование - не так уж много для решения любой из этих проблем:

  1. При переходе к другому обработчику, потому что у вас закончились данные для обработки, вы ... такжепереключение контекстаЭто не переключение потоков, но вам все равно нужно перейти на совершенно другую страницу памяти, а в современной архитектуре доступ к той части памяти, которая отсутствует в кэш-памяти, занимает много времени.Вы просто торгуете «переключателем контекста потока» на «переключение контекста кэша страницы памяти», и вы ничего не получили.

  2. Допустим, вы какое-то приложение для чата,и вы получили от одного из подключенных клиентов сообщение для отправки.Теперь вам нужно запросить БД, чтобы узнать, есть ли у этого пользователя права на публикацию этого сообщения на канал чата, который он намеревается отправить, а также чтобы узнать, есть ли какие-либо другие устройства последующего режима, которые вам нужно обновить.Поскольку это блокирующая операция, вы хотите перейти на другую работу во время ожидания.Но вам нужно как-то запомнить это состояние: отправляющий пользователь, сообщение, результаты ваших запросов к БД.В многопоточной модели эти данные автоматически и неявно заботятся о вас: они находятся в этом стековом пространстве.Если вы используете полный NIO, вам нужно управлять этим самостоятельно, например, с помощью ByteBuffers.

Да, когда вы вручную управляете байтовыми буферами, вы можете сделать их точно такими же большими, как онидолжно быть, и, как правило, это будет гораздо меньше, чем 1 МБ, так что вы можете обрабатывать больше одновременных подключений таким образом.или вы просто выбрасываете 64 ГБ оперативной памяти на своем сервере.

Прагматический результат заключается в следующем:

  1. Код NIO писать чрезвычайно сложно.Используйте абстракции, такие как гризли или нетти, потому что это ракетостроение.

  2. Это редко быстрее.

  3. Вы можете делать больше вещей одновременно, если объем данных, которые необходимо отслеживать для соединения / файла / задания / и т. д., мал.

  4. Это немного похоже на использование ассемблера вместо C, потому что вы можете техническиповышайте производительность вручную, собирая мусор, а не позволяйте java делать это за вас.Но есть причина, по которой большинство людей не использует ассемблер для программирования, даже если это теоретически быстрее.Есть причина, по которой подавляющее большинство веб-приложений написаны на java, или на python, или на node.js, или на каком-то другом высоком уровне, а не на неуправляемом языке, таком как C (++) или ассемблер.

0 голосов
/ 01 февраля 2019

Вопрос "Как внутри Java работает NIO?"слишком широк для StackOverflow, но вопрос о пуле потоков не таков.

Я создал сетевую инфраструктуру под названием SimpleNet , которую я хотел бы использовать в качествепример ответа на ваш вопрос, поскольку он использует классы, такие как AsynchronousServerSocketChannel, AsynchronousSocketChannel и т. д.

executor = new ThreadPoolExecutor(numThreads, numThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), runnable -> {
    Thread thread = new Thread(runnable);
    thread.setDaemon(false);
    return thread;
});

executor.prestartAllCoreThreads();

channel = AsynchronousServerSocketChannel.open(AsynchronousChannelGroup.withThreadPool(executor));

В приведенном выше фрагменте кода, взятого из моего проекта, вы можете видеть, что AsynchronousServerSocketChannel#openпринимает AsynchronousChannelGroup, где вы можете передать пользовательский ThreadPoolExecutor (который является ExecutorService).

Итак, чтобы ответить на ваш вопрос: да, пул потоков используется для обработки завершения ввода-вывода, дажес Asynchronous* классами NIO.

Примечание : Это может измениться, как только проект Project Loom завершится и Волокна захватят весь мир.

...