Коли використовується пул потоків?


104

Тож я розумію, як працює Node.js: у нього є одна нитка слухача, яка отримує подію, а потім делегує її до пулу працівників. Робоча нитка повідомляє слухача, як тільки він закінчить роботу, а слухач повертає відповідь абоненту.

Моє запитання таке: якщо я встаю HTTP-сервер у Node.js і зателефоную до режиму сну на одному з подій мого маршруту (наприклад, "/ тест / сон"), вся система припиняється. Навіть одна нитка слухача. Але я розумів, що цей код відбувається на пулі працівників.

Зараз, навпаки, коли я використовую Mongoose для розмови з MongoDB, зчитування БД - це дорога операція вводу / виводу. Схоже, вузол може делегувати роботу до потоку та отримувати зворотний дзвінок після його завершення; час, необхідний для завантаження з БД, схоже, не блокує систему.

Як Node.js вирішує використовувати нитку пулу потоків проти потоку слухача? Чому я не можу написати код події, який спить і блокує лише нитку пулу потоків?


@Tobi - я це бачив. Це все ще не відповідає на моє запитання. Якби робота була над іншою темою, сон впливав би лише на цю нитку, а не на слухача.
Хені

8
Справжнє запитання, де ви намагаєтесь зрозуміти щось самостійно, і коли не можете знайти вихід у лабіринт, ви звертаєтесь за допомогою.
Рафаель Ейн

Відповіді:


240

Ваше розуміння того, як працює вузол, не є правильним ... але це поширене помилкове уявлення, адже реальність ситуації насправді досить складна і зазвичай зводиться до жалюгідних маленьких фраз на кшталт "вузол є єдиною ниткою", що надто спрощує речі .

На даний момент ми будемо ігнорувати явну багатообробну / багатопотокову передачу через кластерні та веб-робочі нитки , а також просто поговоримо про типовий непотоковий вузол.

Вузол працює в одному циклі подій. Це одна різьба, і ви отримуєте лише одну нитку. Весь JavaScript, який ви пишете, виконується в цьому циклі, і якщо операція блокування відбудеться в цьому коді, він заблокує весь цикл, і більше нічого не відбудеться, поки він не закінчиться. Це, як правило, одиночний характер вузла, про який ви так багато чуєте. Але, це не вся картина.

Деякі функції та модулі, як правило, написані на C / C ++, підтримують асинхронний введення / виведення. Коли ви викликаєте ці функції та методи, вони внутрішньо керують передачею дзвінка на робочу нитку. Наприклад, коли ви використовуєте fsмодуль для запиту файлу, fsмодуль передає цей виклик робочій нитці, і той працівник чекає його відповіді, який потім повертається до циклу подій, який без нього запускається в тим часом. Все це абстрагується подалі від вас, розробника вузла, а частина цього абстрагується від розробників модулів за допомогою libuv .

Як вказував Денис Доллфус у коментарях (з цієї відповіді на аналогічне запитання), стратегія, яку використовує libuv для досягнення асинхронного вводу-виводу, не завжди є ниткою пулу, зокрема у випадку з httpмодулем, як видається, є інша стратегія використовується в цей час. Для наших цілей тут головне важливо відзначити, як досягається асинхронний контекст (за допомогою libuv) і що пул потоків, підтримуваний libuv, є однією з безлічі стратегій, пропонованих цією бібліотекою для досягнення асинхронності.


У дотичній дотичній структурі, що здебільшого пов'язана, у цій чудовій статті є більш глибокий аналіз того, як вузол досягає асинхронності, а також деякі пов'язані з цим потенційні проблеми та як з ними боротися . Більшість з них розширюється на те, що я писав вище, але додатково вказує:

  • Будь-який зовнішній модуль, який ви включаєте у свій проект, який використовує нативний C ++ та libuv, швидше за все, буде використовувати пул потоків (подумайте: доступ до бази даних)
  • У libuv за замовчуванням розмір пулу потоків становить 4, і він використовує чергу для управління доступом до пулу потоків - підсумок полягає в тому, що якщо у вас є 5 тривалих запитів БД, які працюють одночасно, один з них (і будь-який інший асинхронний дія, яка спирається на пул потоків) буде чекати, коли ці запити завершаться, перш ніж вони навіть розпочнуться
  • Ви можете пом'якшити це, збільшивши розмір пулу потоків через UV_THREADPOOL_SIZEзмінну оточення, доки це не зробите до того, як буде потрібен і створений пул потоків:process.env.UV_THREADPOOL_SIZE = 10;

Якщо ви хочете традиційну багатообробну чи багатопотокову передачу у вузлі, ви можете отримати її через вбудований clusterмодуль або різні інші модулі, такі як вищезгаданий webworker-threads, або ви можете підробити це, застосувавши певний спосіб з’єднання вашої роботи та вручну за допомогою setTimeoutабо setImmediateабо process.nextTickпризупинити роботу та продовжити її в подальшому циклі, щоб інші процеси завершилися (але це не рекомендується).

Зауважте, якщо ви пишете довго запущений / блокуючий код у JavaScript, ви, ймовірно, помиляєтесь. Інші мови працюватимуть набагато ефективніше.


1
Святе лайно, це мене повністю очищає. Дуже дякую @Jason!
Хені

5
Немає проблем :) Я опинився там, де ви не так давно, і важко було підійти до чітко визначеної відповіді, оскільки з одного боку у вас є дистрибутори C / C ++, для яких відповідь очевидна, а з іншого - типова веб-розробники, які раніше не надто глибоко вникали в подібні питання. Я навіть не впевнений, що моя відповідь на 100% технічно правильна, коли ви опускаєтесь до рівня С, але це правильно в широких штрихах.
Джейсон

3
Використання пулу потоків для мережевих запитів було б величезним марнотратством. Відповідно до цього питання "Це робить мережу вводу / виводу асинхронної бази на основі інтерфейсів асинхронного вводу / виводу на різних платформах, таких як epoll, kqueue та IOCP, без пулу потоків" - що має сенс.
Деніс Доллфус

1
... це означає, що якщо ви робите важкий підйом в основному потоці javascript безпосередньо, або у вас недостатньо ресурсів або не керуєте ними належним чином, щоб дати достатньо місця для заготівлі, ви можете ввести відставання з меншою одночасністю Поріг - підсумок полягає в тому, що для одних і тих же системних ресурсів ти, як правило, відчуваєш більш високу тягу з node.js, ніж з іншими параметрами (хоча є й інші події на інших мовах, які спрямовані на те, щоб кинути виклик цьому - я не побачили останні показники) - зрозуміло, що модель, заснована на подіях, перевершує потокову модель.
Джейсон

1
@Aabid Потік слухача не виконує запит до бази даних, тому для завершення всіх 10 цих запитів знадобиться приблизно 6 секунд (за розміром пулу потоків потоків за замовчуванням 4). Якщо вам потрібно виконати будь-яку роботу в JavaScript, яка не вимагає завершення результатів запиту бази даних, наприклад, надходить більше запитів, які не потребують асинхронної роботи, виконаної пулом потоків, вона продовжить працювати в основному цикл подій.
Джейсон

20

Тож я розумію, як працює Node.js: у нього є одна нитка слухача, яка отримує подію, а потім делегує її до пулу працівників. Робоча нитка повідомляє слухача, як тільки він закінчить роботу, а слухач повертає відповідь абоненту.

Це не дуже точно. У Node.js є лише одна робоча нитка, яка виконує javascript. Всередині вузла є потоки, які обробляють обробку вводу-виводу, але вважати їх "робочими" - помилкове уявлення. Насправді є просто обробка IO та кілька інших деталей внутрішньої реалізації вузла, але як програміст ви не можете впливати на їх поведінку, окрім кількох параметрів різного типу, таких як MAX_LISTENERS.

Моє запитання таке: якщо я встаю HTTP-сервер у Node.js і зателефоную до режиму сну на одному з подій мого маршруту (наприклад, "/ тест / сон"), вся система припиняється. Навіть одна нитка слухача. Але я розумів, що цей код відбувається на пулі працівників.

У JavaScript не існує механізму сну. Ми могли б обговорити це конкретніше, якби ви розмістили фрагмент коду того, що, на вашу думку, означає «сон». Наприклад, немає такої функції, щоб викликати щось, наприклад, time.sleep(30)у python. Там, setTimeoutале це принципово НЕ спить. setTimeoutі setIntervalявно випускають , а не блокують цикл подій, тому інші біти коду можуть виконуватись в основному потоці виконання. Єдине, що ви можете зробити, це зайняти цикл процесора з обчисленням в пам'яті, яке дійсно почне голодувати основним потоком виконання і не призведе до відмови вашої програми.

Як Node.js вирішує використовувати нитку пулу потоків проти потоку слухача? Чому я не можу написати код події, який спить і блокує лише нитку пулу потоків?

Мережевий IO завжди асинхронний. Кінець історії. Дисковий IO має як синхронний, так і асинхронний API, тому "рішення" немає. node.js поводитиметься відповідно до основних функцій API, які ви називаєте синхронізацією та нормальною асинхронією. Наприклад: fs.readFileпроти fs.readFileSync. Для дочірніх процесів також існують окремі child_process.execта child_process.execSyncAPI.

Правилом є завжди використання асинхронних API. Вагомі причини використання API синхронізації полягають у ініціалізації коду в мережевій службі перед прослуховуванням з'єднань або у простих сценаріях, які не приймають мережеві запити на інструменти побудови тощо.


1
Звідки беруться ці асинхронні API? Я розумію, що ви говорите, але той, хто написав цей API, увійшов у режим IOCP / async. Як вони вирішили це зробити?
Хені

3
Його питання полягає в тому, як він буде писати свій власний інтенсивний код, а не блокувати.
Джейсон

1
Так. Вузол забезпечує базові мережі UDP, TCP та HTTP. Він пропонує ТІЛЬКИ асинхронні API на базі пулу. Весь код node.js у світі без винятку використовує ці асинхронні API на базі пулу, оскільки є просто все, що є в наявності. Файлова система та дочірні процеси - це інша історія, але мережа послідовно асинхронна.
Пітер Ліонс

4
Обережно, Пітер, щоб ти не був прихильним горщиком до його чайника. Він хоче знати, як це робили письменники мережевого API, а не як люди, які використовують мережевий API. Зрештою, я зрозумів, як поводиться вузол повторно: неблокуючі події, тому що я хотів написати власний неблокуючий код, який не має нічого спільного з мережевими або будь-якими іншими вбудованими асинхронними API. Цілком зрозуміло, що Девід хоче зробити те саме.
Джейсон

2
Вузол не використовує пули потоків для IO, він використовує вбудований неблокуючий IO, єдиний виняток - fsнаскільки я знаю
vkurchatkin

2

Пул ниток, як коли і хто використовував:

По-перше, коли ми використовуємо / встановлюємо Node на комп’ютер, він запускає процес серед інших процесів, який називається процесом вузла в комп'ютері, і він продовжує працювати, поки ви не вб'єте його. І цей запущений процес - це наш так званий єдиний потік.

введіть тут опис зображення

Таким чином, механізм одного потоку дозволяє легко блокувати додаток вузла, але це одна з унікальних особливостей, яку Node.js вносить у таблицю. Отже, знову ж таки, якщо ви запустите додаток для вузла, він запуститься лише в одному потоці. Незалежно від того, чи маєте ви один або мільйон користувачів одночасно звертатися до вашої програми.

Тож давайте розберемося, що саме відбувається в одному потоці nodejs, коли ви запускаєте додаток для вузла. Спочатку програма ініціалізується, потім виконується весь код верхнього рівня, а це означає, що всі коди, які не знаходяться в жодній функції зворотного виклику ( пам'ятайте, що всі коди все функції зворотного виклику будуть виконуватися під циклом подій ).

Після цього всі коди модулів виконуються, потім реєструють весь зворотний виклик, нарешті, цикл подій запускається для вашої програми.

введіть тут опис зображення

Отже, як ми обговорюємо, перш ніж всі функції зворотного виклику та коди всередині цих функцій виконуватимуться в циклі подій. У циклі подій навантаження розподіляються по різних фазах. У всякому разі, я не збираюся тут обговорювати петлю подій.

Ну а для того, щоб краще зрозуміти пул Thread, я прошу вас уявити, що в циклі подій коди всередині однієї функції зворотного виклику виконуються після завершення виконання кодів всередині іншої функції зворотного виклику, тепер, якщо є якісь завдання, насправді занадто важкі. Потім вони заблокують наш nodejs єдиним потоком. Отож, саме там входить пул потоків, який подібно до циклу подій, надається Node.js бібліотекою libuv.

Отже пул потоків не є частиною самих nodejs, він надається libuv для вивантаження важких обов'язків на libuv, і libuv виконує ці коди у власних потоках, а після виконання libuv поверне результати події у циклі подій.

введіть тут опис зображення

Пул ниток дає нам чотири додаткові нитки, ці повністю відокремлені від основної єдиної нитки. І ми можемо насправді налаштувати його до 128 потоків.

Отже всі ці нитки разом утворили пул ниток. і цикл подій може автоматично завантажувати важкі завдання в пул потоків.

Весела частина - це все відбувається автоматично за лаштунками. Це не ми, розробники, які вирішують, що йде в пул потоків, а що ні.

Є багато завдань, що надходять до пулу потоків, наприклад,

-> All operations dealing with files
->Everyting is related to cryptography, like caching passwords.
->All compression stuff
->DNS lookups

0

Це непорозуміння є лише різницею між попередньою багатозадачністю та кооперативною багатозадачністю ...

Сон вимикає весь карнавал, тому що дійсно є одна лінія на всі атракціони, і ви закрили ворота. Подумайте про це як про "інтерпретатора JS та деякі інші речі" і ігноруйте теми ... для вас є лише одна нитка, ...

... тому не блокуйте це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.