Петля подій Nodejs


141

Чи є внутрішньо дві петлі подій в архітектурі nodejs?

  • libev / libuv
  • v8 цикл подій javascript

На запит вводу / виводу читання вузлів виконує запит на libeio, який, в свою чергу, повідомляє про доступність даних через події за допомогою libev, і, нарешті, ці події обробляються циклом подій v8 за допомогою зворотних зворотних дзвінків?

В основному, як libev і libeio інтегруються в архітектуру nodejs?

Чи є наявна документація, щоб дати чітке уявлення про внутрішню архітектуру nodejs?

Відповіді:


175

Я особисто читав вихідний код node.js & v8.

Я зіткнувся з подібною проблемою, як ви, коли я намагався зрозуміти архітектуру node.js для того, щоб написати рідні модулі.

Що я тут публікую, це моє розуміння node.js, і це може бути трохи поза увагою.

  1. Libev - це цикл подій, який фактично працює внутрішньо в node.js для виконання простих операцій циклу подій. Він написаний спочатку для * nix систем. Libev забезпечує простий, але оптимізований цикл подій для запуску процесу. Більше про libev можна прочитати тут .

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

  3. LibUv - шар абстракції у верхній частині libeio, libev, c-ares (для DNS) та iocp (для windows asynchronous-io). LibUv виконує, підтримує та керує всіма іо та подіями в пулі подій. (у разі libeio threadpool). Слід переглянути підручник Райана Даля на libUv. Це почне мати більше сенсу для вас, як сам libUv працює, і тоді ви зрозумієте, як node.js працює у верхній частині libuv та v8.

Щоб зрозуміти лише цикл подій Javascript, ви повинні розглянути ці відео

Щоб побачити, як libeio використовується з node.js для створення модулів асинхронізації, ви повинні побачити цей приклад .

В основному, те, що відбувається всередині node.js, полягає в тому, що цикл v8 працює і обробляє всі частини JavaScript, а також модулі C ++ [коли вони працюють в основному потоці (відповідно до офіційної документації, сама node.js є одиночною ниткою)]. Коли поза основною ниткою, libev і libeio обробляють її в пулі ниток і libev забезпечують взаємодію з основною петлею. Отже, з мого розуміння, node.js має 1 постійний цикл подій: це цикл подій v8. Для обробки завдань асинхронізації C ++ використовується за допомогою потокового пулу [via libeio & libev].

Наприклад:

eio_custom(Task,FLAG,AfterTask,Eio_REQUEST);

Що з'являється у всіх модулях, зазвичай викликає функцію Taskв нитковій пулі. Коли вона завершена, вона викликає AfterTaskфункцію в основному потоці. Тоді Eio_REQUESTяк обробник запиту може бути структурою / об'єктом, мотивом якого є забезпечення зв'язку між нитковим пулом та основною ниткою.


Покладаючись на те, що libuv використовує libev всередині, це хороший спосіб зробити ваш код не крос-платформенним. Вам слід дбати лише про публічний інтерфейс libuv.
Райнос

1
@Raynos libuv спрямований на те, щоб переконатися, що його x-platfousing декілька бібліотек. Правильно? отже, використання libuv є гарною ідеєю
ShrekOverflow

1
@Abhishek From Doc process.nextTick- У наступному циклі навколо циклу події викликайте цей зворотний дзвінок. Це не простий псевдонім setTimeout (fn, 0), це набагато ефективніше. На який цикл подій це стосується? V8 цикл подій?
Таміл


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

20

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

Дозвольте спробувати пояснити роботу моделі, керованої подіями, за допомогою абстрактного прикладу, в абстрактному середовищі UNIX, в контексті Вузла, на сьогодні.

Перспектива програми:

  • Двигун сценаріїв починає виконання сценарію.
  • Щоразу, коли трапляється пов'язана з процесором операція, вона виконується в режимі inline (справжня машина) у повноті.
  • Щоразу, коли зустрічається операція, пов'язана з входом / виводом, запит та його обробник завершуються, реєструються в 'апараті подій' (віртуальна машина)
  • Повторіть операції тим же способом, що описано вище, поки не закінчиться сценарій. Операція, пов'язана з процесором - виконайте вбудовані, вбудовані / вхідні запити, запит до обладнання, як зазначено вище.
  • Коли введення / виведення завершено, слухачі передзвонюються.

Машина подій, що знаходиться вище, називається рамкою циклу подій Abu libuv. Вузол використовує цю бібліотеку для реалізації її моделі програмування, керованої подіями.

Точка вузла:

  • Майте одну нитку для розміщення часу виконання.
  • Підберіть сценарій користувача.
  • Скомпілюйте його в рідний [leverage v8]
  • Завантажте двійковий код і заскочіть у точку входу.
  • Скомпільований код виконує послідовно пов'язані з процесором дії, використовуючи примітиви програмування.
  • Багато кодів, пов'язаних з введенням-виведеннями та таймером, мають натурні обгортання. Наприклад, мережевий введення / виведення.
  • Таким чином, виклики вводу / виводу переводяться з сценарію на мости C ++, при цьому в якості аргументів передається ручка вводу / виводу та обробник завершення.
  • Нативний код здійснює цикл libuv. Він набуває циклу, запускає подія низького рівня, яка представляє введення-виведення, і вбудовану обгортку зворотного виклику в структуру циклу libuv.
  • Народний код повертається до сценарію - на даний момент ніякого вводу-виводу не відбувається!
  • Елементи вище повторюються багато разів, поки не буде виконаний весь код, який не входить / виходить, і весь код вводу / виводу буде зареєстрований.
  • Нарешті, коли в системі нічого не залишається виконати, вузол передає керування libuv
  • libuv вступає в дію, він підбирає всі зареєстровані події, запитує операційну систему, щоб отримати їх працездатність.
  • Ті, які готові до вводу / виводу в режимі, що не блокує, підбираються, виконується введення / виведення та видаються зворотні виклики. Один за одним.
  • Ті, які ще не готові (наприклад, зчитування сокета, для якого інша кінцева точка ще нічого не написала), продовжуватимуть досліджуватися з ОС, поки вони не стануть доступними.
  • Цикл внутрішньо підтримує постійно зростаючий таймер. Коли додатки вимагають відкладеного зворотного дзвінка (наприклад, setTimeout), це внутрішнє значення таймера використовується для обчислення правильного часу для запуску зворотного виклику.

Хоча таким чином функціонує більшість функціональних можливостей, деякі (асинхронні версії) файлових операцій здійснюються за допомогою додаткових потоків, добре інтегрованих у libuv. Хоча операції з мережевого вводу-виводу можуть чекати в очікуванні зовнішньої події, такої як інша кінцева точка, що реагує на дані тощо. Операції з файлами потребують певної роботи від самого вузла. Наприклад, якщо ви відкриєте файл і дочекаєтеся, коли fd буде готовий до даних, цього не станеться, оскільки насправді ніхто не читає! У той же час, якщо ви читаєте з файлу, вбудованого в основний потік, він потенційно може блокувати інші дії в програмі і може створювати видимі проблеми, оскільки операції з файлами дуже повільні в порівнянні з процесами, пов'язаними з процесором. Таким чином, внутрішні потоки робітників (налаштовані за допомогою змінної середовища UV_THREADPOOL_SIZE) використовуються для роботи над файлами,

Сподіваюся, це допомагає.


Як ви знали ці речі, чи можете ви вказати мені на джерело?
Сурай Джайн

18

Вступ до libuv

Проект node.js розпочався у 2009 році як середовище JavaScript, від'єднане від браузера. Використовуючи V8 від Google і марк Леманн , node.js поєднав модель введення / виводу - парний - з мовою, яка добре підходила до стилю програмування; завдяки тому, як його формували браузери. Оскільки node.js зростав у популярності, важливо було змусити його працювати в Windows, але libev працював лише на Unix. Еквівалент механізмів сповіщення про події ядра, як-от kqueue або (e) опитування, є IOCP. libuv - це абстракція навколо libev або IOCP залежно від платформи, надаючи користувачам API на основі libev. У версії node-v0.9.0 libuv libev було видалено .

Також одне зображення, яке описує цикл подій у Node.js від @ BusyRich


Оновлення 09.09.2017

Відповідно до цього циклу подій у документі Node.js ,

Наступна схема показує спрощений огляд порядку операцій циклу подій.

   ┌───────────────────────┐
┌─>│        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐         incoming:   
           poll          │<─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘

Примітка: кожне поле буде називатися "фазою" циклу подій.

Огляд фаз

  • таймери : ця фаза виконує зворотні виклики, заплановані до setTimeout()та setInterval().
  • Зворотні виклики вводу / виводу : виконує майже всі зворотні виклики за винятком закритих зворотних викликів , тих, які заплановані таймерами та setImmediate().
  • простоювати, готувати : використовується тільки внутрішньо.
  • опитування : отримання нових подій вводу-виводу; вузол заблокує тут, коли це доречно.
  • перевірити : setImmediate()тут викликаються зворотні дзвінки.
  • закриті зворотні дзвінки : напрsocket.on('close', ...) .

Між кожним запуском циклу подій, Node.js перевіряє, чи він чекає будь-яких асинхронних таймерів вводу / виводу або таймерів, та чи закривається, якщо таких немає.


Ви цитували це " In the node-v0.9.0 version of libuv libev was removed", але опису про це у nodejs немає changelog. github.com/nodejs/node/blob/master/CHANGELOG.md . А якщо libev видалено, то як тепер в nodejs виконуються асинхронні введення / виведення?
інтехаб

@intekhab, За цим посиланням , я думаю, що libuv, заснований на libeio, може бути використаний як цикл подій у node.js.
zangw

@intekhab Я думаю, що libuv реалізує всі функції, пов'язані з введенням-виведенням та опитуванням. ось перевірте цього документа: docs.libuv.org/en/v1.x/loop.html
mohit kaushik

13

В архітектурі NodeJs є одна петля подій.

Модель циклу подій Node.js

Вузольні програми працюють у однопотоковій моделі, керованій подіями. Однак Node реалізує пул потоків у фоновому режимі, щоб можна було виконувати роботу.

Node.js додає роботу до черги подій, а потім має єдиний потік, на якому працює цикл подій. Цикл подій захоплює верхній елемент у черзі подій, виконує його, а потім захоплює наступний елемент.

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

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

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

Коли подія, яка блокує введення-виведення, витягується з черги подій, Node.js отримує потік з пулу потоків і виконує там функцію замість основного потоку циклу події. Це запобігає блокуванню вводу-виводу від затримки решти подій у черзі подій.



1

Як початківець javascript, я також мав те ж сумніви, чи NodeJS містить 2 петлі подій ?. Після довгих досліджень та обговорень з одним із учасників V8 я отримав наступні концепції.

  • Цикл подій є фундаментальною абстрактною концепцією моделі програмування JavaScript. Таким чином, V8 двигун забезпечує реалізацію за замовчуванням для циклу подій, який вбудовувачі (браузер, вузол) можуть замінити або розширити . Ви, хлопці, можете знайти реалізацію циклу подій за замовчуванням V8 тут
  • У NodeJS існує лише один цикл подій , який забезпечується під час виконання вузла. Реалізацію циклу подій за замовчуванням V8 було замінено на реалізацію циклу подій NodeJS

0

Ця pbkdf2функція має реалізацію JavaScript, але вона фактично делегує всю роботу, яку потрібно виконати стороні C ++.

env->SetMethod(target, "pbkdf2", PBKDF2);
  env->SetMethod(target, "generateKeyPairRSA", GenerateKeyPairRSA);
  env->SetMethod(target, "generateKeyPairDSA", GenerateKeyPairDSA);
  env->SetMethod(target, "generateKeyPairEC", GenerateKeyPairEC);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_NAMED_CURVE);
  NODE_DEFINE_CONSTANT(target, OPENSSL_EC_EXPLICIT_CURVE);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS1);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingPKCS8);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSPKI);
  NODE_DEFINE_CONSTANT(target, kKeyEncodingSEC1);
  NODE_DEFINE_CONSTANT(target, kKeyFormatDER);
  NODE_DEFINE_CONSTANT(target, kKeyFormatPEM);
  NODE_DEFINE_CONSTANT(target, kKeyTypeSecret);
  NODE_DEFINE_CONSTANT(target, kKeyTypePublic);
  NODE_DEFINE_CONSTANT(target, kKeyTypePrivate);
  env->SetMethod(target, "randomBytes", RandomBytes);
  env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual);
  env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers);
  env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers);
  env->SetMethodNoSideEffect(target, "getHashes", GetHashes);
  env->SetMethodNoSideEffect(target, "getCurves", GetCurves);
  env->SetMethod(target, "publicEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_encrypt_init,
                                         EVP_PKEY_encrypt>);
  env->SetMethod(target, "privateDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_decrypt_init,
                                         EVP_PKEY_decrypt>);
  env->SetMethod(target, "privateEncrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPrivate,
                                         EVP_PKEY_sign_init,
                                         EVP_PKEY_sign>);
  env->SetMethod(target, "publicDecrypt",
                 PublicKeyCipher::Cipher<PublicKeyCipher::kPublic,
                                         EVP_PKEY_verify_recover_init,
                                         EVP_PKEY_verify_recover>);

ресурс: https://github.com/nodejs/node/blob/master/src/node_crypto.cc

Модуль Libuv несе ще одну відповідальність, що стосується деяких особливо функцій у стандартній бібліотеці.

Для деяких стандартних викликів функцій бібліотеки сторона Node C ++ та Libuv вирішують повністю робити дорогі обчислення поза циклом подій.

Замість цього вони використовують щось, що називається пул потоків, пул потоків - це серія з чотирьох потоків, які можна використовувати для виконання обчислювально дорогих завдань, таких як pbkdf2функція.

За замовчуванням Libuv створює 4 потоки в цьому пулі потоків.

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

Багато функцій, включених у стандартну бібліотеку Node, автоматично використовують цей пул потоків. pbkdf2Функція є однією з них.

Наявність цього пулу ниток дуже важливе.

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

Якщо пул подій відповідав за виконання обчислювально дорогого завдання, то наш додаток Node не міг би нічого іншого.

Наш процесор виконує всі інструкції всередині потоку по черзі.

Використовуючи пул потоків, ми можемо робити інші речі всередині циклу події під час проведення розрахунків.

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