이벤트 루프는 Node.js의 기본 부분입니다. 이벤트 루프를 이해하는 것은 효율적인 애플리케이션을 구축하는 데 중요합니다. 다음 기사는 Node.js의 이벤트 루프에 대한 심층적인 이해를 제공할 것입니다. 도움이 되기를 바랍니다!
당신은 한동안 Node.js를 사용해왔고, 몇 가지 앱을 구축하고, 다양한 모듈을 시험해 보았고, 심지어 비동기 프로그래밍에도 익숙해졌습니다. 하지만 뭔가가 계속해서 당신을 괴롭히고 있습니다. 바로 이벤트 루프입니다.
당신도 나와 같다면 이벤트 루프를 이해하기 위해 문서를 읽고 비디오를 시청하는 데 셀 수 없이 많은 시간을 보냈을 것입니다. 그러나 숙련된 개발자라도 작동 방식을 완전히 이해하는 데 어려움을 겪을 수 있습니다. 이것이 제가 Node.js 이벤트 루프를 완전히 이해하는 데 도움이 되도록 이 시각적 가이드를 준비한 이유입니다. 그러니 편안히 앉아 커피 한 잔을 마시고 Node.js 이벤트 루프의 세계로 뛰어들어 봅시다. [관련 튜토리얼 추천:nodejs 비디오 튜토리얼,프로그래밍 교육]
JavaScript의 비동기 프로그래밍에 대한 리뷰부터 시작하겠습니다. JavaScript는 웹, 모바일 및 데스크톱 애플리케이션에서 사용되지만 핵심적으로JavaScript는 동기식 차단 단일 스레드 언어라는 점을 기억하는 것이 중요합니다. 짧은 코드 조각을 통해 이 문장을 이해해 보겠습니다.
// index.js function A() { console.log("A"); } function B() { console.log("B"); } A() B() // Logs A and then B
콘솔에 메시지를 기록하는 두 개의 함수가 있는 경우 코드는 위에서 아래로 한 번에 한 줄씩 실행됩니다. 위의 코드 조각에서 A가 B보다 먼저 기록되는 것을 볼 수 있습니다.
JavaScript가 동기식 특성으로 인해 차단 중입니다. 이전 프로세스가 얼마나 오래 걸리더라도 이전 프로세스가 완료될 때까지 후속 프로세스는 시작되지 않습니다. 코드 조각에서 함수 A가 큰 코드 블록을 실행해야 하는 경우 JavaScript는 함수 B로 분기하지 않고 해당 작업을 완료해야 합니다. 이 코드 조각이 10초, 심지어 1분 정도 걸리더라도 말이죠.
브라우저에서 이런 상황이 발생했을 수 있습니다. 웹 애플리케이션이 브라우저에서 실행 중이고 브라우저에 제어권을 반환하지 않고 집중적인 코드 블록을 실행할 때 브라우저가 정지될 수 있으며 이를 차단이라고 합니다. 웹 애플리케이션이 브라우저에 프로세서 제어를 반환할 때까지 브라우저는 계속해서 사용자 입력을 처리하고 다른 작업을 수행하지 못하도록 차단됩니다.
스레드는 JavaScript 프로그램이 작업을 실행하는 데 사용할 수 있는 프로세스입니다. 각 스레드는 한 번에 하나의 작업만 수행할 수 있습니다. 멀티스레딩을 지원하고 여러 작업을 동시에 실행할 수 있는 다른 언어와 달리 JavaScript에는 코드를 실행하는 메인 스레드라는 스레드가 하나만 있습니다.
상상할 수 있듯이 이 JavaScript 모델은 코드 실행을 계속하기 전에 데이터를 가져올 때까지 기다려야 하기 때문에 문제를 일으킵니다. 이 대기 시간은 몇 초 정도 걸릴 수 있으며 그 동안에는 다른 코드를 실행할 수 없습니다. JavaScript가 기다리지 않고 계속 처리되면 오류가 발생합니다. JavaScript에서 비동기 동작을 구현해야 합니다. Node.js로 가서 살펴보겠습니다.
Node.js 런타임은 브라우저를 사용하지 않고도 JavaScript 프로그램을 사용하고 실행할 수 있는 환경입니다. Core - 노드 런타임은 세 가지 주요 구성 요소로 구성됩니다.
모든 부분이 중요하지만 Node.js의 비동기 프로그래밍의 핵심 구성 요소는 libuv입니다.
Libuv는 C 언어로 작성된 크로스 플랫폼 오픈 소스 라이브러리입니다. Node.js 런타임에서 해당 역할은 비동기 작업 처리를 지원하는 것입니다. 어떻게 작동하는지 살펴보겠습니다.
让我们来概括一下代码在 Node 运行时中的执行方式。在执行代码时,位于图片左侧的 V8 引擎负责 JavaScript 代码的执行。该引擎包含一个内存堆(Memory heap)和一个调用栈(Call stack)。
每当声明变量或函数时,都会在堆上分配内存。执行代码时,函数就会被推入调用栈中。当函数返回时,它就从调用栈中弹出了。这是对栈数据结构的简单实现,最后添加的项是第一个被移除。在图片右侧,是负责处理异步方法的 libuv。
每当我们执行异步方法时,libuv 接管任务的执行。然后使用操作系统本地异步机制运行任务。如果本地机制不可用或不足,则利用其线程池来运行任务,并确保主线程不被阻塞。
首先,让我们来看一下同步代码执行。以下代码由三个控制台日志语句组成,依次记录“First”,“Second”和“Third”。我们按照运行时执行顺序来查看代码。
// index.js console.log("First"); console.log("Second"); console.log("Third");
以下是 Node 运行时执行同步代码的可视化展示。
执行的主线程始终从全局作用域开始。全局函数(如果我们可以这样称呼它)被推入堆栈中。然后,在第 1 行,我们有一个控制台日志语句。这个函数被推入堆栈中。假设这个发生在 1 毫秒时,“First” 被记录在控制台上。然后,这个函数从堆栈中弹出。
执行到第 2 行时。假设到第 2 毫秒了,log 函数再次被推入堆栈中。“Second”被记录在控制台上,并弹出该函数。
最后,执行到第 3 行了。第 3 毫秒时,log 函数被推入堆栈,“Third”将记录在控制台上,并弹出该函数。此时已经没有代码要执行,全局也被弹出。
接下来,让我们看一下异步代码执行。有以下代码片段:包含三个日志语句,但这次第二个日志语句传递给了fs.readFile()
作为回调函数。
执行的主线程始终从全局作用域开始。全局函数被推入堆栈。然后执行到第 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 用于处理 Node.js 中的异步操作。对于像处理网络请求这样的异步操作,libuv 依赖于操作系统原生机制。对于没有本地 OS 支持的异步读取文件的操作,libuv 则依赖其线程池以确保主线程不被阻塞。然而,这也引发了一些问题。
setTimeout
和setInterval
这类延迟执行回调函数的方法又是何时执行回调函数呢?setTimeout
和readFile
这类异步任务同时完成,Node 如何决定哪个回调函数先在调用栈上运行?其中一个会有更多的优先级吗?所有这些问题都可以通过理解 libuv 核心部分——事件循环来得到答案。
기술적으로 말하면 이벤트 루프는 C 언어 프로그램일 뿐입니다. 하지만 Node.js에서는 동기 코드와 비동기 코드의 실행을 조정하기 위한 디자인 패턴으로 생각할 수 있습니다.
이벤트 루프는 Node.js 애플리케이션이 실행되는 동안 실행되는 루프입니다. 각 루프에는 6개의 서로 다른 대기열이 있으며, 각 대기열에는 최종적으로 호출 스택에서 실행되어야 하는 하나 이상의 콜백 함수가 포함되어 있습니다.
setTimeout
및setInterval
콜백 함수와 관련하여 저장되는 타이머 큐(기술적으로 min-heap이라고 함)가 있습니다.setTimeout
和
setInterval
相关的回调函数。
fs
和http
模块中提供的相关方法。setImmediate
函数相关的回调函数,这是特定于Node 的功能。最后,有两个不同队列组成微任务队列(microtask queue)。
process.nextTick
函数关联的回调函数。Promise
相关联的回调函数。需要注意的是计时器、I/O、检查和关闭队列都属于 libuv。然而,两个微任务队列并不属于 libuv。尽管如此,它们仍然是 Node 运行时环境中扮演着重要角色,并且在执行回调顺序方面发挥着重要作用。说到这里, 让我们来理解一下事件循环是如何工作的。
图中箭头是一个提示,但可能还不太容易理解。让我来解释一下队列的优先级顺序。首先要知道,所有用户编写的同步 JavaScript 代码都比异步代码优先级更高。这表示只有在调用堆栈为空时,事件循环才会发挥作用。
在事件循环中,执行顺序遵循某些规则。需要掌握的规则还是有一些的,我们逐个的了解一下:
此时,如果还有更多的回调需要处理,那么事件循环再运行一次(译注:事件循环在程序运行期间一直在运行,在当前没有可供处理的任务情况下,会处于等待状态,一旦有新任务就会执行),并重复相同的步骤。另一方面,如果所有回调都已执行并且没有更多代码要处理(译注:也就是程序执行结束),则事件循环退出。
这就是 libuv 事件循环在 Node.js 中执行异步代码的作用。有了这些规则,我们可以重新审视之前提出的问题。
当一个异步任务在 libuv 中完成时,什么时候 Node 会在调用栈上运行相关联的回调函数?
答案:只有当调用栈为空时才执行回调函数。
Node 是否会等待调用栈为空后再运行回调函数?还是打断正常执行流来运行回调函数?
答案:运行回调函数时不会打断正常执行流。
像setTimeout
和setInterval
두 번째로fs
및http
모듈과 같은 모든 비동기 메서드와 관련된 콜백 함수가 포함된 I/O 대기열이 있습니다. 에서 제공되는 관련 메서드.세 번째는 노드별 함수인setImmediate
함수와 관련된 콜백 함수를 보유하는 확인 대기열입니다.
process.nextTick
함수와 관련된 콜백 함수를 저장합니다. Promise 대기열은 JavaScript의 로컬
Promise
와 관련된 콜백 함수를 저장합니다. 타이머, I/O, 대기열 확인 및 닫기는 모두 libuv에 속한다는 점에 유의해야 합니다. 그러나 두 개의 마이크로태스크 대기열은 libuv에 속하지 않습니다. 그럼에도 불구하고 이들은 여전히 Node 런타임 환경에서 중요한 역할을 하며 콜백이 실행되는 순서에서 중요한 역할을 합니다. 그렇다면 이벤트 루프가 어떻게 작동하는지 이해해 봅시다.
setTimeout
및setInterval
과 같은 메서드는 언제 실행되나요?답변:setTimeout
및setInterval
은 마이크로태스크 대기열에 관계없이 모든 콜백 함수 중에서 첫 번째 우선순위로 실행됩니다.setTimeout
和setInterval
的所有回调函数中第一优先级执行的(不考虑微任务队列)。
如果两个异步任务(例如setTimeout
和readFile
두 개의 비동기 작업(예:setTimeout
및readFile
)이 동시에 완료되면 Node는 호출 스택에서 어떤 콜백 함수가 먼저 실행될지 어떻게 결정합니까? 하나가 다른 것보다 우선순위가 더 높습니까?
지금까지 많은 것을 배웠지만 아래 그림에 표시된 실행 순서를 염두에 두시기 바랍니다. Node.js가 뒤에서 비동기 코드를 실행하는 방법을 완벽하게 보여주기 때문입니다.
결론
노드 관련 지식을 더 보려면 nodejs 튜토리얼을 방문하세요!위 내용은 Node의 이벤트 루프에 대해 이야기해 봅시다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!