Якщо вам доведеться задати це питання, ви, мабуть, незнайомі з тим, чим займаються більшість веб-додатків / послуг. Ви, напевно, думаєте, що все програмне забезпечення робить це:
user do an action
│
v
application start processing action
└──> loop ...
└──> busy processing
end loop
└──> send result to user
Однак це не так, як працюють веб-програми, або взагалі будь-які додатки з базою даних як бек-енд. Веб-додатки роблять це:
user do an action
│
v
application start processing action
└──> make database request
└──> do nothing until request completes
request complete
└──> send result to user
У цьому випадку програмне забезпечення витрачає більшу частину свого робочого часу, використовуючи 0% часу процесора в очікуванні повернення бази даних.
Додаток для багатопотокових мереж:
Багатопотокові мережеві програми обробляють вищенаведене навантаження таким чином:
request ──> spawn thread
└──> wait for database request
└──> answer request
request ──> spawn thread
└──> wait for database request
└──> answer request
request ──> spawn thread
└──> wait for database request
└──> answer request
Таким чином, потоки проводять більшу частину свого часу, використовуючи 0% процесора, чекаючи, коли база даних поверне дані. При цьому їм довелося виділити пам'ять, необхідну для потоку, що включає повністю окремий стек програми для кожного потоку тощо. дешево.
Однополосний цикл подій
Оскільки ми проводимо більшу частину часу, використовуючи 0% процесора, чому б не запустити якийсь код, коли ми не використовуємо процесор? Таким чином, кожен запит все одно отримає стільки ж часу процесора, скільки багатопотокові програми, але нам не потрібно запускати потік. Отже ми робимо це:
request ──> make database request
request ──> make database request
request ──> make database request
database request complete ──> send response
database request complete ──> send response
database request complete ──> send response
На практиці обидва підходи повертають дані приблизно з однаковою затримкою, оскільки саме час обробки реакції бази даних домінує в обробці.
Основна перевага тут полягає в тому, що нам не потрібно зароджувати нову нитку, тому нам не потрібно робити багато і багато молоток, який би сповільнив нас.
Чарівна, невидима нитка
На перший погляд загадкова річ - як обидва підходи, описані вище, встигають виконувати навантаження "паралельно"? Відповідь полягає в тому, що база даних є потоковою. Отже, наша однопоточна програма фактично використовує багатопотокове поведінку іншого процесу: бази даних.
Там, де односмуговий підхід не вдається
Додаток з однопотоковим зв’язком виходить з ладу, якщо вам потрібно зробити багато обчислень процесора перед поверненням даних. Тепер я не маю на увазі циклічну обробку результатів бази даних. Це все ще переважно O (n). Я маю на увазі такі речі, як перетворення Фур'є (кодування mp3, наприклад), трасування променів (3D-рендерінг) тощо.
Ще одна помилка однопотокових додатків полягає в тому, що він буде використовувати лише одне ядро CPU. Отже, якщо у вас є чотириядерний сервер (не рідкість сьогодні), ви не використовуєте інші 3 ядра.
Там, де багатопотоковий підхід не вдається
Багатопотокове додаток не вдається отримати велику кількість, якщо вам потрібно виділити багато оперативної пам’яті на потік. По-перше, саме використання оперативної пам'яті означає, що ви не можете обробити стільки запитів, як однопотокове додаток. Гірше, малок повільний. Виділення великої кількості об'єктів (що є звичайним для сучасних веб-рамок) означає, що ми можемо бути більш повільними, ніж однопоточні програми. Тут зазвичай виграють node.js.
Один із випадків використання, які в результаті покращують багатопотоковість, - це коли потрібно запустити іншу мову сценаріїв у своїй темі. Спочатку вам зазвичай потрібно провести весь час виконання цієї мови, а потім потрібно змістити змінні, використовувані вашим сценарієм.
Тож якщо ви пишете мережеві додатки на C або go або java, то накладні нарізки зазвичай не будуть занадто поганими. Якщо ви пишете веб-сервер C для обслуговування PHP або Ruby, тоді написати швидший сервер дуже просто в JavaScript або Ruby або Python.
Гібридний підхід
Деякі веб-сервери використовують гібридний підхід. Наприклад, Nginx і Apache2 реалізують свій мережевий код обробки як пул потоків циклів подій. Кожен потік виконує цикл подій, одночасно обробляючи запити однопотокових, але запити врівноважуються між кількома потоками.
Деякі архітектури з однопотоковими схемами також використовують гібридний підхід. Замість запуску декількох потоків з одного процесу ви можете запустити кілька додатків - наприклад, 4 сервери node.js на чотирьохядерній машині. Потім ви використовуєте балансир навантаження, щоб розподілити навантаження між процесами.
По суті, два підходи є технічно однаковими дзеркальними зображеннями один одного.