Блокуюче I/O
Що таке блокуючі виклики?
Блокуючі виклики в Java - це операції вводу/виводу (I/O), які зупиняють виконання поточного потоку, доки дані не будуть повністю прочитані або записані. Наприклад, при читанні даних з файлу або при виконанні мережевого запиту.
Що відбувається з потоком під час блокуючого виклику?
Під час мережевого запиту, якщо дані ще не готові, потік, який виконує блокуючий виклик, заморожується. Це означає, що він не може виконувати інші завдання або обробляти інші запити, поки не отримає відповідь.
Який системний виклик використовується?
Під капотом, для мережевих операцій, використовуються системні виклики, такі як `read()` та `write()` в Unix-подібних системах. Ці виклики звертаються до операційної системи для виконання I/O операцій.
Чому ми чекаємо?
Потік чекає, бо ОС повинна виконати операцію I/O, яка може вимагати часу - наприклад, чекати на отримання даних з мережі або завершення читання диска.
Неблокуюче I/O
Що таке неблокуючий виклик?
Неблокуюче I/O дозволяє потоку продовжувати виконання, навіть коли дані ще не готові. Замість того, щоб зупинятися і чекати, потік може перевірити статус операції і, якщо дані недоступні, зайнятися іншими завданнями.
Неблокуючі мережеві системні виклики ядра
Системи, які підтримують неблокуюче I/O, використовують механізми, такі як `epoll` в Linux, `kqueue` в BSD, які дозволяють потокам ефективно моніторити кілька I/O потоків без блокування.
1. epoll у Linux:
epoll - це механізм введення-виведення, який дозволяє ефективно моніторити кілька файлових дескрипторів.
Він використовується для реалізації неблокуючих і асинхронних I/O операцій.
epoll працює з подією, підписуючись на різні типи подій для кожного дескриптора, і повідомляє програму, коли ці події настають, замість того, щоб чекати на завершення I/O.
2. kqueue у BSD:
kqueue - аналогічний механізм, який використовується в BSD-подібних системах.
Він також дозволяє моніторити різноманітні події, включаючи файли, сокети та таймери.
kqueue ефективно обробляє велику кількість подій, зменшуючи навантаження на систему.
В Java NIO з’явилася можливість створити потік, який знатиме, який канал готовий для запису та читання даних і може обробляти цей конкретний канал. Це реалізується за допомогою класу Selector.
Уявімо, що у вас є сервер, який одночасно обслуговує багато клієнтів через мережу. Замість того, щоб створювати новий потік для кожного клієнта (що може бути неефективним та витратним з точки зору ресурсів), ви використовуєте один потік і Selector.
Selector працює як централізований менеджер, який стежить за всіма з'єднаннями. Коли клієнт надсилає запит до сервера, Selector визначає, яке з'єднання готове до обробки (наприклад, прийому або відправки даних). Це дозволяє серверу ефективно переключатися між багатьма з'єднаннями, використовуючи невелику кількість потоків.
Цей підхід особливо корисний у ситуаціях, де сервер повинен одночасно обробляти значну к-ть з'єднань, але кожне з'єднання активно лише частину часу — веб-сервери, чат-сервера, ігри, etc.
Неблокуюче I/O дозволяє потоку продовжувати виконання інших завдань, поки очікується на введення-виведення. Це підвищує загальну ефективність, дозволяючи одному потоку обробляти декілька I/O операцій одночасно, не даючи потоку “простоювати“.