>웹 프론트엔드 >JS 튜토리얼 >Node의 이벤트 루프에 대해 이야기해 봅시다.

Node의 이벤트 루프에 대해 이야기해 봅시다.

青灯夜游
青灯夜游앞으로
2023-04-11 19:08:211596검색

이벤트 루프는 Node.js의 기본 부분입니다. 이벤트 루프를 이해하는 것은 효율적인 애플리케이션을 구축하는 데 중요합니다. 다음 기사는 Node.js의 이벤트 루프에 대한 심층적인 이해를 제공할 것입니다. 도움이 되기를 바랍니다!

Node의 이벤트 루프에 대해 이야기해 봅시다.

당신은 한동안 Node.js를 사용해왔고, 몇 가지 앱을 구축하고, 다양한 모듈을 시험해 보았고, 심지어 비동기 프로그래밍에도 익숙해졌습니다. 하지만 뭔가가 계속해서 당신을 괴롭히고 있습니다. 바로 이벤트 루프입니다.

당신도 나와 같다면 이벤트 루프를 이해하기 위해 문서를 읽고 비디오를 시청하는 데 셀 수 없이 많은 시간을 보냈을 것입니다. 그러나 숙련된 개발자라도 작동 방식을 완전히 이해하는 데 어려움을 겪을 수 있습니다. 이것이 제가 Node.js 이벤트 루프를 완전히 이해하는 데 도움이 되도록 이 시각적 가이드를 준비한 이유입니다. 그러니 편안히 앉아 커피 한 잔을 마시고 Node.js 이벤트 루프의 세계로 뛰어들어 봅시다. [관련 튜토리얼 추천: nodejs 비디오 튜토리얼, 프로그래밍 교육]

JavaScript의 비동기 프로그래밍

JavaScript의 비동기 프로그래밍에 대한 리뷰부터 시작하겠습니다. JavaScript는 웹, 모바일 및 데스크톱 애플리케이션에서 사용되지만 핵심적으로 JavaScript는 동기식 차단 단일 스레드 언어라는 점을 기억하는 것이 중요합니다. 짧은 코드 조각을 통해 이 문장을 이해해 보겠습니다.

// index.js

function A() {
  console.log("A");
}

function B() {
  console.log("B");
}

A()
B()

// Logs A and then B

JavaScript는 동기식입니다

콘솔에 메시지를 기록하는 두 개의 함수가 있는 경우 코드는 위에서 아래로 한 번에 한 줄씩 실행됩니다. 위의 코드 조각에서 A가 B보다 먼저 기록되는 것을 볼 수 있습니다.

JavaScript가 차단 중입니다.

JavaScript가 동기식 특성으로 인해 차단 중입니다. 이전 프로세스가 얼마나 오래 걸리더라도 이전 프로세스가 완료될 때까지 후속 프로세스는 시작되지 않습니다. 코드 조각에서 함수 A가 큰 코드 블록을 실행해야 하는 경우 JavaScript는 함수 B로 분기하지 않고 해당 작업을 완료해야 합니다. 이 코드 조각이 10초, 심지어 1분 정도 걸리더라도 말이죠.

브라우저에서 이런 상황이 발생했을 수 있습니다. 웹 애플리케이션이 브라우저에서 실행 중이고 브라우저에 제어권을 반환하지 않고 집중적인 코드 블록을 실행할 때 브라우저가 정지될 수 있으며 이를 차단이라고 합니다. 웹 애플리케이션이 브라우저에 프로세서 제어를 반환할 때까지 브라우저는 계속해서 사용자 입력을 처리하고 다른 작업을 수행하지 못하도록 차단됩니다.

JavaScript는 단일 스레드입니다.

스레드는 JavaScript 프로그램이 작업을 실행하는 데 사용할 수 있는 프로세스입니다. 각 스레드는 한 번에 하나의 작업만 수행할 수 있습니다. 멀티스레딩을 지원하고 여러 작업을 동시에 실행할 수 있는 다른 언어와 달리 JavaScript에는 코드를 실행하는 메인 스레드라는 스레드가 하나만 있습니다.

JavaScript를 기다리는 중

상상할 수 있듯이 이 JavaScript 모델은 코드 실행을 계속하기 전에 데이터를 가져올 때까지 기다려야 하기 때문에 문제를 일으킵니다. 이 대기 시간은 몇 초 정도 걸릴 수 있으며 그 동안에는 다른 코드를 실행할 수 없습니다. JavaScript가 기다리지 않고 계속 처리되면 오류가 발생합니다. JavaScript에서 비동기 동작을 구현해야 합니다. Node.js로 가서 살펴보겠습니다.

Node.js 런타임

Node의 이벤트 루프에 대해 이야기해 봅시다.

Node.js 런타임은 브라우저를 사용하지 않고도 JavaScript 프로그램을 사용하고 실행할 수 있는 환경입니다. Core - 노드 런타임은 세 가지 주요 구성 요소로 구성됩니다.

  • V8, libuv, crypto 등과 같은 외부 종속성은 Node.js의 필수 기능입니다.
  • C++ 기능은 파일 시스템 액세스 및 네트워킹과 같은 기능을 제공합니다.
  • JavaScript 라이브러리는 JavaScript 코드를 사용하여 C++ 기능을 쉽게 호출할 수 있는 기능과 도구를 제공합니다.

모든 부분이 중요하지만 Node.js의 비동기 프로그래밍의 핵심 구성 요소는 libuv입니다.

Libuv

Libuv는 C 언어로 작성된 크로스 플랫폼 오픈 소스 라이브러리입니다. Node.js 런타임에서 해당 역할은 비동기 작업 처리를 지원하는 것입니다. 어떻게 작동하는지 살펴보겠습니다.

Node.js 런타임에서 코드 실행

Node의 이벤트 루프에 대해 이야기해 봅시다.

让我们来概括一下代码在 Node 运行时中的执行方式。在执行代码时,位于图片左侧的 V8 引擎负责 JavaScript 代码的执行。该引擎包含一个内存堆(Memory heap)和一个调用栈(Call stack)。

每当声明变量或函数时,都会在堆上分配内存。执行代码时,函数就会被推入调用栈中。当函数返回时,它就从调用栈中弹出了。这是对栈数据结构的简单实现,最后添加的项是第一个被移除。在图片右侧,是负责处理异步方法的 libuv。

每当我们执行异步方法时,libuv 接管任务的执行。然后使用操作系统本地异步机制运行任务。如果本地机制不可用或不足,则利用其线程池来运行任务,并确保主线程不被阻塞。

同步代码执行

首先,让我们来看一下同步代码执行。以下代码由三个控制台日志语句组成,依次记录“First”,“Second”和“Third”。我们按照运行时执行顺序来查看代码。

// index.js
console.log("First");
console.log("Second");
console.log("Third");

以下是 Node 运行时执行同步代码的可视化展示。

Node의 이벤트 루프에 대해 이야기해 봅시다.

执行的主线程始终从全局作用域开始。全局函数(如果我们可以这样称呼它)被推入堆栈中。然后,在第 1 行,我们有一个控制台日志语句。这个函数被推入堆栈中。假设这个发生在 1 毫秒时,“First” 被记录在控制台上。然后,这个函数从堆栈中弹出。

执行到第 2 行时。假设到第 2 毫秒了,log 函数再次被推入堆栈中。“Second”被记录在控制台上,并弹出该函数。

最后,执行到第 3 行了。第 3 毫秒时,log 函数被推入堆栈,“Third”将记录在控制台上,并弹出该函数。此时已经没有代码要执行,全局也被弹出。

异步代码执行

接下来,让我们看一下异步代码执行。有以下代码片段:包含三个日志语句,但这次第二个日志语句传递给了fs.readFile() 作为回调函数。

Node의 이벤트 루프에 대해 이야기해 봅시다.

执行的主线程始终从全局作用域开始。全局函数被推入堆栈。然后执行到第 1 行,在第 1 毫秒时,“First”被记录在控制台中,并弹出该函数。然后执行移动到第 2 行,在第 2毫秒时,readFile 方法被推入堆栈。由于 readFile 是异步操作,因此它会转移(off-loaded)到 libuv。

JavaScript 从调用堆栈中弹出了 readFile 方法,因为就第 2 行的执行而言,它的工作已经完成了。在后台,libuv 开始在单独的线程上读取文件内容。在第 3 毫秒时,JavaScript 继续进行到第 5 行,将 log 函数推入堆栈,“Third”被记录到控制台中,并将该函数弹出堆栈。

大约在第 4 毫秒左右,假设文件读取任务已经完成,则相关回调函数现在会在调用栈上执行, 在回调函数内部遇到 log 函数。

log 函数推入到到调用栈,“Second”被记录到控制台并弹出 log 函数 。由于回调函数中没有更多要执行的语句,因此也被弹出 。没有更多代码可运行了 ,所以全局函数也从堆栈中删除 。

控制台输出“First”,“Third”,然后是“Second”。

Libuv 和异步操作

很明显,libuv 用于处理 Node.js 中的异步操作。对于像处理网络请求这样的异步操作,libuv 依赖于操作系统原生机制。对于没有本地 OS 支持的异步读取文件的操作,libuv 则依赖其线程池以确保主线程不被阻塞。然而,这也引发了一些问题。

  • 当一个异步任务在 libuv 中完成时,什么时候 Node 会在调用栈上运行相关联的回调函数?
  • Node 是否会等待调用栈为空后再运行回调函数?还是打断正常执行流来运行回调函数?
  • setTimeoutsetInterval 这类延迟执行回调函数的方法又是何时执行回调函数呢?
  • 如果 setTimeoutreadFile 这类异步任务同时完成,Node 如何决定哪个回调函数先在调用栈上运行?其中一个会有更多的优先级吗?

所有这些问题都可以通过理解 libuv 核心部分——事件循环来得到答案。

이벤트 루프란 무엇인가요?

기술적으로 말하면 이벤트 루프는 C 언어 프로그램일 뿐입니다. 하지만 Node.js에서는 동기 코드와 비동기 코드의 실행을 조정하기 위한 디자인 패턴으로 생각할 수 있습니다.

Visual Event Loop

이벤트 루프는 Node.js 애플리케이션이 실행되는 동안 실행되는 루프입니다. 각 루프에는 6개의 서로 다른 대기열이 있으며, 각 대기열에는 최종적으로 호출 스택에서 실행되어야 하는 하나 이상의 콜백 함수가 포함되어 있습니다.

Node의 이벤트 루프에 대해 이야기해 봅시다.

  • 먼저 setTimeoutsetInterval 콜백 함수와 관련하여 저장되는 타이머 큐(기술적으로 min-heap이라고 함)가 있습니다.
  • setTimeoutsetInterval 相关的回调函数。
  • 其次,有一个 I/O 队列(I/O queue),其中包含与所有异步方法相关的回调函数,例如 fshttp 模块中提供的相关方法。
  • 第三个是检查队列(check queue),它保存与 setImmediate 函数相关的回调函数,这是特定于Node 的功能。
  • 第四个是关闭队列(close queue),它保存与异步任务关闭事件相关联的回调函数。

最后,有两个不同队列组成微任务队列(microtask queue)。

  • nextTick 队列保存了与 process.nextTick 函数关联的回调函数。
  • Promise 队列则保存了JavaScript 中本地 Promise 相关联的回调函数。

需要注意的是计时器、I/O、检查和关闭队列都属于 libuv。然而,两个微任务队列并不属于 libuv。尽管如此,它们仍然是 Node 运行时环境中扮演着重要角色,并且在执行回调顺序方面发挥着重要作用。说到这里, 让我们来理解一下事件循环是如何工作的。

事件循环是如何工作的?

图中箭头是一个提示,但可能还不太容易理解。让我来解释一下队列的优先级顺序。首先要知道,所有用户编写的同步 JavaScript 代码都比异步代码优先级更高。这表示只有在调用堆栈为空时,事件循环才会发挥作用。

在事件循环中,执行顺序遵循某些规则。需要掌握的规则还是有一些的,我们逐个的了解一下:

  1. 执行微任务队列(microtask queue)中的所有回调函数。首先是 nextTick 队列中的任务,然后是 Promise 队列中的任务。
  2. 执行计时器队列(timer queue)内的所有回调函数。
  3. 如果微任务队列中存在回调函数,则在计时器队列内每执行完一次回调函数之后执行微任务队列中的所有回调函数。首先是 nextTick 队列中的任务,然后是 Promise 队列中的任务。
  4. 执行 I/O 队列(I/O queue)内的所有回调函数。
  5. 如果微任务队列中存在回调函数,按照先 nextTick 队列后 Promise 队列的顺序依次执行微任务队列中的所有回调函数。
  6. 执行检查队列(check queue)内的所有回调函数。
  7. 如果微任务队列中存在回调函数,则在检查队列内每个回调之后执行微任务队列中的所有回调函数 。首先是 nextTick 队列中的任务,然后是 Promise 队列中的任务。
  8. 执行关闭队列(close queue)内的所有回调函数。
  9. 在同一循环的最后,再执行一次微任务队列。首先是 nextTick 队列中的任务,然后是 Promise 队列中的任务。

此时,如果还有更多的回调需要处理,那么事件循环再运行一次(译注:事件循环在程序运行期间一直在运行,在当前没有可供处理的任务情况下,会处于等待状态,一旦有新任务就会执行),并重复相同的步骤。另一方面,如果所有回调都已执行并且没有更多代码要处理(译注:也就是程序执行结束),则事件循环退出。

这就是 libuv 事件循环在 Node.js 中执行异步代码的作用。有了这些规则,我们可以重新审视之前提出的问题。


当一个异步任务在 libuv 中完成时,什么时候 Node 会在调用栈上运行相关联的回调函数?

答案:只有当调用栈为空时才执行回调函数。

Node 是否会等待调用栈为空后再运行回调函数?还是打断正常执行流来运行回调函数?

答案:运行回调函数时不会打断正常执行流。

setTimeoutsetInterval두 번째로 fshttp 모듈과 같은 모든 비동기 메서드와 관련된 콜백 함수가 포함된 I/O 대기열이 있습니다. 에서 제공되는 관련 메서드. 세 번째는 노드별 함수인 setImmediate 함수와 관련된 콜백 함수를 보유하는 확인 대기열입니다.

네 번째는 비동기 작업 닫기 이벤트와 관련된 콜백 함수를 저장하는 닫기 대기열입니다. 🎜마지막으로 마이크로태스크 대기열을 형성하는 두 개의 서로 다른 대기열이 있습니다. 🎜🎜🎜nextTick 대기열은 process.nextTick 함수와 관련된 콜백 함수를 저장합니다. 🎜Promise 대기열은 JavaScript의 로컬 Promise와 관련된 콜백 함수를 저장합니다. 🎜타이머, I/O, 대기열 확인 및 닫기는 모두 libuv에 속한다는 점에 유의해야 합니다. 그러나 두 개의 마이크로태스크 대기열은 libuv에 속하지 않습니다. 그럼에도 불구하고 이들은 여전히 ​​Node 런타임 환경에서 중요한 역할을 하며 콜백이 실행되는 순서에서 중요한 역할을 합니다. 그렇다면 이벤트 루프가 어떻게 작동하는지 이해해 봅시다. 🎜

🎜이벤트 루프는 어떻게 작동하나요? 🎜🎜🎜사진 속 화살표는 힌트인데 이해가 쉽지 않을 수도 있어요. 대기열 우선순위에 대해 설명하겠습니다. 가장 먼저 알아야 할 점은 사용자가 작성한 모든 동기 JavaScript 코드가 비동기 코드보다 우선한다는 것입니다. 즉, 이벤트 루프는 호출 스택이 비어 있을 때만 작동합니다. 🎜🎜이벤트 루프에서는 실행 순서가 특정 규칙을 따릅니다. 아직 마스터해야 할 몇 가지 규칙이 있습니다. 하나씩 살펴보겠습니다. 🎜
    🎜마이크로태스크 대기열에서 모든 콜백 함수를 실행하세요. 먼저 nextTick 대기열의 작업, 그 다음 Promise 대기열의 작업입니다. 🎜타이머 대기열의 모든 콜백 함수를 실행합니다. 🎜마이크로태스크 대기열에 콜백 함수가 있는 경우 타이머 대기열🎜에서 각 콜백 함수가 실행된 후 마이크로태스크 대기열의 모든 콜백 함수가 실행됩니다. 먼저 nextTick 대기열의 작업, 그 다음 Promise 대기열의 작업입니다. 🎜I/O 대기열의 모든 콜백 함수를 실행합니다. 🎜마이크로태스크 대기열에 콜백 함수가 있는 경우 마이크로태스크 대기열의 모든 콜백 함수는 nextTick 대기열, Promise 대기열 순서로 순차적으로 실행됩니다. 🎜체크 큐의 모든 콜백 함수를 실행합니다. 🎜마이크로태스크 대기열에 콜백 함수가 있는 경우, 대기열의 각 콜백을 🎜확인한 후 마이크로태스크 대기열의 모든 콜백 함수가 실행됩니다. 먼저 nextTick 대기열의 작업, 그 다음 Promise 대기열의 작업입니다. 🎜닫기 대기열의 모든 콜백 함수를 실행합니다. 🎜동일 루프가 끝나면 마이크로태스크 대기열을 다시 실행하세요. 먼저 nextTick 대기열의 작업, 그 다음 Promise 대기열의 작업입니다.
🎜이때 처리할 콜백이 더 있으면 이벤트 루프가 다시 실행됩니다. (주석: 프로그램이 실행되는 동안 이벤트 루프는 계속 실행되며 현재 처리할 작업이 없습니다. 다음에는 대기 상태에 있다가 새로운 작업이 생기면 바로 실행됩니다), 같은 과정을 반복합니다. 반면에 모든 콜백이 실행되고 더 이상 처리할 코드가 없으면 이벤트 루프가 종료됩니다. 🎜🎜이것은 Node.js에서 비동기 코드를 실행하기 위해 libuv 이벤트 루프가 수행하는 작업입니다. 이러한 규칙을 활용하면 앞서 질문했던 질문을 다시 살펴볼 수 있습니다. 🎜
🎜🎜libuv에서 비동기 작업이 완료되면 Node는 언제 호출 스택에서 관련 콜백 함수를 실행합니까? 🎜🎜🎜답변: 콜백 함수는 콜 스택이 비어 있을 때만 실행됩니다. 🎜🎜🎜 노드는 콜백 함수를 실행하기 전에 호출 스택이 비워질 때까지 기다리나요? 아니면 콜백 함수를 실행하기 위해 일반적인 실행 흐름을 중단하시겠습니까? 🎜🎜🎜답변:콜백 함수를 실행해도 정상적인 실행 흐름이 중단되지 않습니다. 🎜🎜🎜콜백 함수 실행을 지연시키는 setTimeoutsetInterval과 같은 메서드는 언제 실행되나요? 🎜🎜

답변: setTimeoutsetInterval은 마이크로태스크 대기열에 관계없이 모든 콜백 함수 중에서 첫 번째 우선순위로 실행됩니다. setTimeoutsetInterval 的所有回调函数中第一优先级执行的(不考虑微任务队列)。

如果两个异步任务(例如 setTimeoutreadFile

두 개의 비동기 작업(예: setTimeoutreadFile)이 동시에 완료되면 Node는 호출 스택에서 어떤 콜백 함수가 먼저 실행될지 어떻게 결정합니까? 하나가 다른 것보다 우선순위가 더 높습니까?

답변:
동시 완료의 경우 타이머 콜백은 I/O 콜백보다 먼저 실행됩니다.

Node의 이벤트 루프에 대해 이야기해 봅시다.지금까지 많은 것을 배웠지만 아래 그림에 표시된 실행 순서를 염두에 두시기 바랍니다. Node.js가 뒤에서 비동기 코드를 실행하는 방법을 완벽하게 보여주기 때문입니다.

그러나 "이 시각화를 확인하는 코드는 어디에 있습니까?"라고 질문할 수 있습니다. 이벤트 루프의 각 큐에는 구현의 미묘한 차이가 있으므로 하나씩 설명하는 것이 좋습니다. 이 기사는 Node.js 이벤트 루프에 관한 시리즈 중 첫 번째 기사입니다. 각 대기열의 작업에 대한 자세한 내용을 이해하려면 기사 끝 부분의 링크를 확인하십시오. 지금 당장 깊은 인상을 받았더라도 특정 시나리오에 도달하면 여전히 함정에 빠질 수 있습니다. .

결론

이 시각적 가이드는 JavaScript, Node.js 런타임 및 비동기 작업을 처리하는 libuv의 비동기 프로그래밍의 기본 사항을 다룹니다. 이러한 지식을 바탕으로 Node.js의 비동기 특성을 활용하는 코드를 작성할 때 도움이 되는 강력한 이벤트 루프 모델을 구축할 수 있습니다.

노드 관련 지식을 더 보려면 🎜nodejs 튜토리얼🎜을 방문하세요! 🎜

위 내용은 Node의 이벤트 루프에 대해 이야기해 봅시다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제