Java NIO — (1) Основополагающий принцип высококонкурентного ввода-вывода и 4 основные модели ввода-вывода.

Java

Серия статей Java NIO

  1. Основополагающий принцип высококонкурентного ввода-вывода и 4 основные модели ввода-вывода
  2. 4 свойства и важные методы Buffer
  3. Класс канала канала
  4. Селектор селектор

Принцип чтения и записи IO

Как мы все знаем, чтение и запись операций ввода-вывода пользовательских программ зависят от чтения и записи операций ввода-вывода операционной системы, и в основном будут использоваться операции чтения и записи операций ввода-вывода.read&writeДва основных системных вызова.

Вот базовые знания:

Системный вызов чтения не считывает данные напрямую с физического устройства в память, а системный вызов записи не записывает данные напрямую на физическое устройство.

Независимо от того, вызывает ли приложение верхнего уровня чтение операционной системы или запись операционной системы, оно будет включать缓冲区. В частности, вызов чтения операционной системы означает передачу данных избуфер ядраскопировать вбуфер процесса; в то время как системный вызов записи предназначен для передачи данных избуфер процессаскопировать вбуфер ядра.

На приведенной выше схеме показана упрощенная «логическая» схема того, как блочные данные перемещаются из внешнего источника (например, жесткого диска) в область хранения внутри запущенного процесса (например, в ОЗУ).

  • Сначала процесс заполняет свой буфер, выполняя системный вызов read().
  • Вызов чтения заставляет ядро ​​выдавать команды аппаратному обеспечению контроллера диска для извлечения данных с диска.
  • Контроллер диска записывает данные непосредственно в буфер памяти ядра через DMA.
  • После того, как контроллер диска заканчивает заполнение буфера, ядро ​​копирует данные из временного буфера в пространстве ядра в буфер, указанный процессом.

Зачем устанавливать так много буферов?

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

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

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

файловый дескриптор

дескриптор файла, также называемый файловым дескриптором. В системе Linux файлы можно разделить на: обычные файлы, файлы каталогов, файлы ссылок и файлы устройств. Файловый дескриптор (File Descriptor) — индекс, созданный ядром для эффективного управления открытыми файлами, представляет собой неотрицательное целое число (обычно небольшое целое число), которое ссылается на открытый файл. Все системные вызовы ввода-вывода, включая вызовы чтения и записи сокетов, выполняются через файловые дескрипторы.

4 основные модели ввода-вывода

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

блокирующий и неблокирующий

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

Синхронный и асинхронный

Синхронный ввод-вывод — это метод инициации ввода-вывода между пространством пользователя и пространством ядра. Синхронный ввод-вывод означает, что поток в пользовательском пространстве является стороной, которая активно инициирует запрос ввода-вывода, а пространство ядра является пассивным получателем. Асинхронный ввод-вывод, с другой стороны, означает, что ядро ​​системы является стороной, которая активно инициирует запрос ввода-вывода, а поток пользовательского пространства является пассивным получателем.

Синхронный блокирующий ввод-вывод (Blocking IO)

В процессе приложения Java по умолчанию все операции ввода-вывода с подключением к сокету являются синхронным блокирующим вводом-выводом (блокирующим вводом-выводом).

В модели блокирующего ввода-вывода приложение Java запускается с системного вызова ввода-вывода до возврата системного вызова, в течение которого процесс Java блокируется. После успешного возврата процесс приложения начинает обрабатывать данные буфера пользовательского пространства.

  1. Из системного вызова чтения, который Java начинает чтение ввода-вывода, пользовательский поток переходит в состояние блокировки.
  2. Когда системное ядро ​​получает системный вызов чтения, оно начинает подготавливать данные. В начале данные могут не достичь буфера ядра (например, не получен полный пакет сокета), и ядро ​​будет ждать в это время.
  3. Ядро ждет, пока не поступят полные данные, оно копирует данные из буфера ядра в пользовательский буфер (память в пользовательском пространстве) и возвращает результат (например, количество байтов, скопированных в пользовательский буфер).
  4. Пока ядро ​​​​не вернется, пользовательский поток будет разблокирован и запущен снова.

image-20201107181041967

Преимущества блокировки ввода-вывода:

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

Недостатки блокировки ввода-вывода:

В нормальных условиях каждое соединение снабжено независимым потоком; с другой стороны, поток поддерживает операции ввода-вывода соединения. В случае низкого параллелизма в этом нет ничего плохого. Однако в сценарии приложения с высокой степенью параллелизма требуется большое количество потоков для поддержки большого количества сетевых подключений, а накладные расходы памяти и переключения потоков будут очень большими. Следовательно, в основном модель блокирующего ввода-вывода непригодна для приложений с высокой степенью параллелизма.

Синхронный неблокирующий NIO (неблокирующий ввод-вывод)

image-20201107181152506

  1. Когда данные ядра не готовы, когда пользовательский поток инициирует запрос ввода-вывода,вернуться сейчас. Итак, чтобы прочитать окончательные данные, пользовательский поток долженПостоянно инициировать системные вызовы ввода-вывода.
  2. После поступления данных ядра пользовательский поток инициирует системный вызов и блокируется. Ядро начинает копирование данных, оно копирует данные из буфера ядра в пользовательский буфер (память в пространстве пользователя), затем ядро ​​возвращает результат (например, возвращает количество байтов, скопированных в пользовательский буфер).
  3. После того, как пользовательский поток прочитает данные, он разблокирует состояние и снова запустится. Другими словами, пользовательский процесс должен много раз пытаться убедиться, что данные действительно прочитаны в конце, а затем продолжить выполнение.

image-20201107181706200

Особенности синхронного неблокирующего ввода-вывода:

Поток приложения должен постоянно выполнять системные вызовы ввода-вывода,голосованиеГотовы ли данные, если нет, продолжайте опрос, пока системный вызов ввода-вывода не завершится.

Преимущества синхронного неблокирующего ввода-вывода:

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

Недостатки синхронного неблокирующего ввода-вывода:

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

Как правило, в сценариях приложений с высокой степенью параллелизма синхронный неблокирующий ввод-вывод также недоступен. Обычные веб-серверы не используют эту модель ввода-вывода. Эта модель ввода-вывода обычно редко используется напрямую, но использует функцию неблокирующего ввода-вывода в других моделях ввода-вывода. В реальной разработке Java эта модель ввода-вывода задействована не будет.

Модель мультиплексирования ввода-вывода (мультиплексирование ввода-вывода)

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

В модели мультиплексирования ввода/вывода вводится новый системный вызов,Запрос состояния готовности IO. В системе Linux соответствующий системный вызовselect/epollсистемный вызов. С помощью этого системного вызова процесс может отслеживать несколькофайловый дескриптор, когда дескриптор готов (обычно буфер ядра доступен для чтения/записи), ядро ​​может вернуть приложению состояние готовности. Впоследствии приложение выполняет соответствующие системные вызовы ввода-вывода в соответствии с состоянием готовности.

В настоящее время системные вызовы, поддерживающие мультиплексирование ввода-вывода, включают select, epoll и т. д. Системный вызов select поддерживается почти во всех операционных системах и обладает хорошими кросс-платформенными функциями. epoll был предложен в ядре Linux 2.6 и представляет собой расширенную для Linux версию системного вызова select.

В модели мультиплексирования ввода-вывода с помощью системного вызова select/epoll поток одного приложения может непрерывно опрашивать сотни или тысячи подключений к сокету.Возвращает соответствующие операции чтения и записи, которые могут быть выполнены.

image-20201107181937528

Возьмите пример, чтобы проиллюстрировать поток модели мультиплексирования ввода-вывода. Системный вызов, который инициирует мультиплексную операцию чтения ввода-вывода, процесс выглядит следующим образом:

  1. регистрация селектора. В этом режиме сначала регистрируется целевое сетевое соединение сокета, которое требует операции чтения, для селектора select/epoll заранее.Соответствующий класс селектора в Java — это класс Selector. Затем можно запустить процесс опроса всей модели мультиплексирования ввода-вывода.

  2. опрос готовности. С помощью метода запроса селектора запросите состояние готовности всех зарегистрированных подключений к сокету. Через системный вызов запроса ядро ​​возвращает список готовых сокетов. Когда данные в любом зарегистрированном сокете готовы и в буфере ядра есть данные (готовые), ядро ​​добавляет сокет в список готовых.

Когда пользовательский процесс вызывает метод запроса select, весь поток будет заблокирован.

  1. После того, как пользовательский поток получает список готовых состояний, он инициирует системный вызов чтения в соответствии с имеющимся в нем сокетным соединением, и пользовательский поток блокируется. Ядро начинает копирование данных, копируя данные из буфера ядра в пользовательский буфер.

  2. После завершения копирования ядро ​​возвращает результат, и пользовательский поток разблокируется, а пользовательский поток считывает данные и продолжает выполняться.

Мультиплексирование ввода-вывода по существу такое же, как неблокирующее ввод-вывод, но использует преимущества новогоselectСистемный вызов, ядро ​​отвечает за операцию опроса, которую запрашивающий процесс должен выполнять сам. Кажется, что накладные расходы на один системный вызов больше, чем при неблокирующем вводе-выводе, но поскольку он можетПоддержка нескольких операций ввода-вывода, что повышает эффективность

Особенности модели мультиплексирования ввода-вывода

  1. Включает два системных вызова (System Call),
  • Один - select/epoll (готовый запрос)
  • Одним из них является операция ввода-вывода.
  1. Подобно модели NIO, мультиплексирование ввода-вывода также требует опроса. Поток, ответственный за вызов запроса статуса select/epoll, должен постоянно выполнять опрос select/epoll, чтобы найти соединение сокета, готовое к операции ввода-вывода.

Преимущества модели мультиплексирования ввода-вывода

По сравнению с блокирующим режимом ввода-вывода, когда один поток поддерживает одно соединение, самым большим преимуществом использования select/epoll является то, что один поток запроса селектора может обрабатывать тысячи соединений (Connection) одновременно. Системе не нужно создавать большое количество потоков или поддерживать эти потоки, что значительно снижает нагрузку на систему.

Недостатки модели мультиплексирования ввода-вывода

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

Если вы хотите полностью разблокировать поток, вы должны использовать асинхронную модель ввода-вывода.

Модель асинхронного ввода-вывода (асинхронный ввод-вывод)

Модель асинхронного ввода-вывода (асинхронный ввод-вывод, называемыйAIO). Основной процесс AIO: пользовательский поток регистрирует операцию ввода-вывода в ядре через системный вызов. После завершения всей операции ввода-вывода (включая подготовку данных и репликацию данных) ядро ​​уведомляет программу пользователя, и пользователь выполняет последующие бизнес-операции.

Например. Системный вызов для инициирования асинхронной операции чтения ввода-вывода, процесс выглядит следующим образом:

  1. Когда пользовательский поток инициирует системный вызов чтения, он может немедленно приступить к другим действиям, и пользовательский поток не блокируется.
  2. Ядро начинает первый этап ввода-вывода: подготовку данных. Когда данные готовы, ядро ​​копирует данные из буфера ядра в пользовательский буфер (память в пространстве пользователя).
  3. Ядро отправит сигнал (Signal) пользовательскому потоку или вызовет обратно интерфейс обратного вызова, зарегистрированный пользовательским потоком, сообщая пользовательскому потоку, что операция чтения завершена.
  4. Пользовательский поток считывает данные из пользовательского буфера и выполняет последующие бизнес-операции.

Особенности модели асинхронного ввода-вывода

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

Недостатки асинхронной модели асинхронного ввода-вывода

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

На данный момент настоящий асинхронный ввод-вывод реализован через IOCP в системе Windows. В системе Linux модель асинхронного ввода-вывода была введена в версии 2.6, и в настоящее время она несовершенна.Основная реализация по-прежнему использует epoll, который аналогичен мультиплексированию ввода-вывода, поэтому очевидного преимущества в производительности нет. Большинство серверных программ с высокой степенью параллелизма обычно основаны на системах Linux. Поэтому в настоящее время при разработке таких сетевых приложений с высокой степенью параллелизма в основном используется модель мультиплексирования ввода-вывода.

Суммировать