>웹 프론트엔드 >JS 튜토리얼 >노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

青灯夜游
青灯夜游앞으로
2023-03-16 20:11:321939검색

메인 스레드는 "작업 대기열"에서 이벤트를 읽습니다. 이 프로세스는 순환적이므로 전체 작동 메커니즘을 이벤트 루프라고도 합니다. 다음 기사는 Node.js의 eventloop를 마스터하는 데 도움이 될 것입니다.

노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

js는 브라우저와 node에서 실행될 수 있지만 이벤트 루프 메커니즘은 동일하지 않습니다. 그리고 큰 차이가 있습니다. js可以在浏览器中执行又可以在node中执行,但是它们的事件循环机制并不是一样的。并且有很大的区别。

EventLoop机制概述

在说Node事件循环机制之前,我们先来讨论两个问题

为什么要学习事件循环机制?

学习事件循环可以让开发者明白JavaScript的运行机制是怎么样的。

事件循环机制做的是什么事情?

事件循环机制用于管理异步API的回调函数什么时候回到主线程中执行

Node.js采用的是异步IO模型。同步API在主线程中执行,异步API在底层的C++维护的线程中执行,异步API的回调函数也会在主线程中执行。【相关教程推荐:nodejs视频教程编程教学

在Javascript应用运行时,众多异步API的回调函数什么时候能回到主线程中调用呢?这就是事件环环机制做的事情,管理异步API的回调函数什么时候回到主线程中执行。

EventLoop的六个阶段

Node中的事件循环分为六个阶段。

在事件循环中的每个阶段都有一个队列,存储要执行的回调函数,事件循环机制会按照先进先出的方式执行他们直到队列为空。

这六个阶段都存储着异步回调函数,所以还是遵循先执行主线程同步代码,当同步代码执行完后再来轮询这六个阶段。

接下来,我们来详细看看这六个阶段里面存储的都是什么

Timers

Timers:用于存储定时器的回调函数(setlnterval,setTimeout)。

Pendingcallbacks

Pendingcallbacks:执行与操作系统相关的回调函数,比如启动服务器端应用时监听端口操作的回调函数就在这里调用。

idle,prepare

idle,prepare:系统内部使用。(这个我们程序员不用管)

Poll

Poll

EventLoop 메커니즘 개요

노드 이벤트 루프 메커니즘에 대해 이야기하기 전에 두 가지 문제를 먼저 논의해 보겠습니다

이벤트 루프 메커니즘을 왜 배워야 할까요?

이벤트 루프를 배우면 개발자는 JavaScript 작동 방식을 이해할 수 있습니다.

    이벤트 루프 메커니즘은 무엇을 합니까?
  • 이벤트 루프 메커니즘은
  • 비동기 API의 콜백 함수가 실행을 위해 메인 스레드로 반환되는 시기를 관리
하는 데 사용됩니다.

Node.js는 비동기 IO 모델을 사용합니다. 동기 API는 메인 스레드에서 실행되고, 비동기 API는 기본 C++에 의해 유지 관리되는 스레드에서 실행되며, 비동기 API의 콜백 함수도 메인 스레드에서 실행됩니다. [권장 관련 튜토리얼: nodejs 동영상 튜토리얼, 프로그래밍 교육]

Javascript 애플리케이션이 실행 중일 때 많은 비동기 API의 콜백 기능은 언제 반환됩니까? 메인 페이지에서 스레드를 호출하는 것은 어떻습니까? 이는 비동기 API의 콜백 함수가 실행을 위해 메인 스레드로 반환되는 시기를 관리하는 이벤트 루프 메커니즘이 수행하는 작업입니다.

Check:存储setlmmediate的回调函数。

Closingcallbacks

Closingcallbacks:执行与关闭事件相关的回调,例如关闭数据库连接的回调函数等。

宏任务与微任务

跟浏览器中的js一样,node中的异步代码也分为宏任务和微任务,只是它们之间的执行顺序有所区别。

我们再来看看Node中都有哪些宏任务和微任务

宏任务

  • setlnterval

  • setimeout

  • setlmmediate

  • I/O

微任务

  • Promise.then

  • Promise.catch

  • Promise.finally

  • process.nextTick

nodeEventLoop의 6단계

🎜 Node의 이벤트 루프는 6단계로 나뉩니다. 🎜🎜🎜🎜이벤트 루프에서 각 단계에는 실행할 콜백 함수를 저장하는 큐가 있으며, 이벤트 루프 메커니즘은 큐가 빌 때까지 선입선출 방식으로 이를 실행합니다. 🎜🎜이 6개 단계는 모두 비동기 콜백 함수를 저장하므로 여전히 메인 스레드 동기화 코드를 먼저 실행하고 동기화 코드가 실행된 후 이 6개 단계를 폴링해야 합니다. 🎜🎜다음으로, 이 6단계에 무엇이 저장되어 있는지 자세히 살펴보겠습니다🎜

🎜Timers🎜🎜🎜Timers: 저장에 사용됨 타이머 콜백 함수 (setinterval, setTimeout). 🎜

🎜Pendingcallbacks🎜🎜🎜Pendingcallbacks: 서버 시작 시 포트 동작을 모니터링하는 콜백 함수 등 운영체제와 관련된 콜백 함수를 실행- 여기에서 호출됩니다. 🎜

🎜idle, prepare🎜🎜🎜idle, prepare: 시스템 내부에서 사용됩니다. (우리 프로그래머들은 이것에 대해 걱정할 필요가 없습니다)🎜

🎜Poll🎜🎜🎜Poll: 1/O 작업을 위한 콜백 함수 대기열을 저장합니다. 파일 읽기 및 쓰기 작업 콜백 함수와 같은. 🎜🎜이 단계에서는 특별한 주의가 필요합니다. 이벤트 큐에 콜백 함수가 있으면 큐가 지워질 때까지 실행하세요. 그렇지 않으면 이벤트 루프는 새 콜백 함수가 입력될 때까지 기다리는 시간 동안 이 단계에 머물게 됩니다. 🎜🎜하지만 이에 대한 🎜대기🎜는 확실하지 않지만 다음 두 가지 조건에 따라 달라집니다. 🎜🎜🎜setlmmediate 대기열에서 실행할 호출 함수가 있는 경우(단계 확인). 이 경우에는 기다리지 않습니다. 🎜🎜타이머 큐에는 실행될 콜백 함수가 있으며, 이 경우 대기는 없습니다. 이벤트 루프는 확인 단계로 이동한 다음 Closingcallbacks 단계로 이동하고 마지막으로 타이머 단계에서 다음 루프로 이동합니다. 🎜🎜🎜🎜Check🎜🎜🎜Check: setlmmediate의 콜백 함수를 저장합니다. 🎜

🎜Closingcallbacks🎜🎜🎜Closingcallbacks: 데이터베이스 연결 닫기를 위한 콜백 함수 등 닫기 이벤트와 관련된 콜백을 실행합니다. 🎜

🎜매크로 작업 및 마이크로 작업🎜

🎜브라우저의 js와 동일, node 비동기 코드 또한 매크로 작업과 마이크로 작업으로 구분되지만 실행 순서가 다릅니다. 🎜🎜노드🎜

🎜매크로 작업🎜🎜
    🎜🎜setlnterval🎜🎜🎜🎜setimeout🎜🎜🎜🎜setlmmediate🎜🎜🎜🎜I/O🎜🎜🎜

    🎜microtasks🎜🎜
      🎜🎜Promise.then🎜🎜🎜🎜Promise.catch🎜🎜🎜🎜Promise.finally🎜🎜🎜🎜process.nextTick🎜🎜🎜🎜 in node, 무엇 마이크로태스크와 매크로태스크의 실행 순서는 무엇입니까? 🎜

      마이크로태스크와 매크로태스크의 실행 순서

      노드에서는 마이크로태스크의 콜백 함수가 마이크로태스크 큐에 배치되고, 매크로태스크의 콜백 함수가 매크로태스크 큐에 배치됩니다. node中,微任务的回调函数被放置在微任务队列中,宏任务的回调函数被放置在宏任务队列中。

      微任务优先级高于宏任务。当微任务事件队列中存在可以执行的回调函数时,事件循环在执行完当前阶段的回调函数后会暂停进入事件循环的下一个阶段,而会立即进入微任务的事件队列中开始执行回调函数,当微任务队列中的回调函数执行完成后,事件循环才会进入到下一个段开始执行回调函数。

      对于微任务我们还有个点需要特别注意。那就是虽然nextTick同属于微任务,但是它的优先级是高于其它微任务,在执行微任务时,只有nextlick中的所有回调函数执行完成后才会开始执行其它微任务。

      总的来说就是当主线程同步代码执行完毕后会优先清空微任务(如果微任务继续产生微任务则会再次清空),然后再到下个事件循环阶段。并且微任务的执行是穿插在事件循环六个阶段中间的,也就是每次事件循环进入下个阶段前会判断微任务队列是否为空,为空才会进入下个阶段,否则先清空微任务队列。

      下面我们用代码实操来验证前面所说的。

      代码实例

      先执行同步再执行异步

      Node应用程序启动后,并不会立即进入事件循环,而是先执行同步代码,从上到下开始执行,同步API立即执行,异步API交给C++维护的线程执行,异步API的回调函数被注册到对应的事件队列中。当所有同步代码执行完成后,才会进入事件循环。

      console.log("start");
      
      setTimeout(() => {
        console.log("setTimeout 1");
      });
      
      setTimeout(() => {
        console.log("setTimeout 2");
      });
      
      console.log("end");

      我们来看执行结果

      노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

      可以看到,先执行同步代码,然后才会进入事件循环执行异步代码,在timers阶段执行两个setTimeout回调。

      setTimeout一定会先于setImmediate执行吗

      我们知道setTimeout是在timers阶段执行,setImmediate是在check阶段执行。并且事件循环是从timers阶段开始的。所以会先执行setTimeout再执行setImmediate

      对于上面的分析一定对吗?

      我们来看例子

      console.log("start");
      
      setTimeout(() => {
        console.log("setTimeout");
      });
      
      setImmediate(() => {
        console.log("setImmediate");
      });
      
      const sleep = (delay) => {
        const startTime = +new Date();
        while (+new Date() - startTime < delay) {
          continue;
        }
      };
      
      sleep(2000);
      console.log("end");

      执行上面的代码,输出如下

      노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

      先执行setTimeout再执行setImmediate

      接下来我们来改造下上面的代码,把延迟器去掉,看看会输出什么

      setTimeout(() => {
        console.log("setTimeout");
      });
      
      setImmediate(() => {
        console.log("setImmediate");
      });

      我们运行了七次,可以看到其中有两次是先运行的setImmediate

      노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

      怎么回事呢?不是先timers阶段再到check阶段吗?怎么会变呢?

      其实这就得看进入事件循环的时候,异步回调有没有完全准备好了。对于最开始的例子,因为有2000毫秒的延迟,所以进入事件循环的时候,setTimeout回调是一定准备好了的。所以执行顺序不会变。但是对于这个例子,因为主线程没有同步代码需要执行,所以一开始就进入事件循环,但是在进入事件循环的时候,setTimeout的回调并不是一定完全准备好的,所以就会有先到check阶段执行setImmediate回调函数,再到下一次事件循环的timers阶段来执行setTimeout的回调。

      那在什么情况下同样的延迟时间,setImmediate回调函数一定会优先于setTimeout的回调呢?

      其实很简单,只要将这两者放到timers阶段和check阶段之间的Pendingcallbacks、idle,prepare、poll阶段中任意一个阶段就可以了。因为这些阶段完执行完是一定会先到check再到timers阶段的。

      我们以poll阶段为例,将这两者写在IO操作中。

      const fs = require("fs");
      
      fs.readFile("./fstest.js", "utf8", (err, data) => {
        setTimeout(() => {
          console.log("setTimeout");
        });
      
        setImmediate(() => {
          console.log("setImmediate");
        });
      });

      我们也来执行七次,可以看到,每次都是setImmediate先执行。

      노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

      所以总的来说,同样的延迟时间,setTimeout并不是百分百先于setImmediate

      마이크로 작업은 매크로 작업보다 우선순위가 높습니다. 마이크로태스크 이벤트 큐에 실행 가능한 콜백 함수가 있는 경우, 이벤트 루프는 일시 중지되어 현재 단계의 콜백 함수를 실행한 후 이벤트 루프의 다음 단계로 들어가고, 즉시 마이크로태스크의 이벤트 큐에 들어가 콜백 실행을 시작합니다. 마이크로태스크 큐의 콜백 함수가 실행되면 이벤트 루프는 다음 세그먼트로 들어가 콜백 함수 실행을 시작합니다. 🎜🎜마이크로 작업에 있어서 특별히 주의해야 할 점이 또 있습니다. 즉, nextTick도 마이크로태스크이지만 다른 마이크로태스크보다 우선순위가 높습니다. 마이크로태스크를 실행할 때 nextlick의 모든 콜백 함수만 실행됩니다. 실행되기 시작합니다. 🎜🎜일반적으로 메인 스레드 동기화 코드가 실행되면 마이크로 태스크가 먼저 지워지고(마이크로 태스크가 계속해서 마이크로 태스크를 생성하면 다시 지워집니다) 다음 이벤트 루프 단계로 이동합니다. 그리고 마이크로태스크의 실행은 이벤트 루프의 6개 단계에 분산되어 있습니다. 즉, 이벤트 루프가 다음 단계로 들어갈 때마다 마이크로태스크 큐가 비어 있는지 확인합니다. 그렇지 않으면 마이크로태스크가 먼저 삭제됩니다. 🎜🎜코드 연습을 통해 위에서 말한 내용을 확인해 보겠습니다. 🎜

      🎜코드 예🎜

      🎜먼저 동기화를 실행한 다음 비동기적으로🎜🎜🎜노드에서 code>애플리케이션이 시작된 후 즉시 이벤트 루프에 진입하지 않고 대신 동기 API가 먼저 실행되고 비동기 API가 실행됩니다. C++에 의해 유지되는 스레드는 비동기 API의 콜백 함수가 해당 이벤트 큐에 등록됩니다. 모든 동기화 코드가 실행되면 이벤트 루프에 진입하게 됩니다. 🎜<pre class="brush:js;toolbar:false;">console.log("start"); setTimeout(() =&gt; { console.log(&quot;setTimeout&quot;); }); setImmediate(() =&gt; { console.log(&quot;setImmediate&quot;); }); Promise.resolve().then(() => { console.log("Promise.resolve"); }); console.log("end");</pre>🎜실행 결과를 살펴보겠습니다🎜🎜<img src="https://img.php.cn/upload/article/000/000/024/8acad95dd96ea6ea6e891129b7f15a0e-1.png" alt="노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해 " loading="lazy">🎜🎜동기 코드가 먼저 실행된 후 이벤트 루프에 들어가 비동기 코드를 실행하는 것을 볼 수 있습니다. <code>setTimeout 콜백 두 개가 에서 실행됩니다. >타이머 단계. 🎜

      🎜setTimeout은 확실히 setImmediate🎜🎜🎜setTimeouttimers 단계에서 실행된다는 것을 알고 있습니다. setImmediate

      check 단계에서 실행됩니다. 그리고 이벤트 루프는 timers 단계에서 시작됩니다. 따라서 setTimeout이 먼저 실행되고 setImmediate가 실행됩니다. 🎜🎜위 분석이 확실히 맞나요? 🎜🎜예제를 살펴보겠습니다🎜
      console.log("start");
      
      setTimeout(() => {
        console.log("setTimeout");
      });
      
      setImmediate(() => {
        console.log("setImmediate");
      });
      
      Promise.resolve().then(() => {
        console.log("Promise.resolve");
      });
      
      process.nextTick(() => {
        console.log("process.nextTick");
      });
      
      console.log("end");
      🎜위 코드를 실행하면 다음과 같이 출력됩니다🎜🎜노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해🎜🎜먼저 setTimeout을 실행한 다음 setImmediate🎜🎜다음으로 위 코드를 변환하여 변경해 보겠습니다. 지연기를 제거하고 결과가 어떻게 나올지 확인하세요🎜
      // timers阶段
      setTimeout(() => {
        console.log("setTimeout");
      
        Promise.resolve().then(() => {
          console.log("setTimeout Promise.resolve");
        });
      });
      
      // check阶段
      setImmediate(() => {
        console.log("setImmediate");
        Promise.resolve().then(() => {
          console.log("setImmediate Promise.resolve");
        });
      });
      
      // 微任务
      Promise.resolve().then(() => {
        console.log("Promise.resolve");
      });
      
      // 微任务
      process.nextTick(() => {
        console.log("process.nextTick");
        Promise.resolve().then(() => {
          console.log("nextTick Promise.resolve");
        });
      });
      🎜7번 실행했는데 그 중 두 번이 먼저 setImmediate🎜🎜노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해🎜🎜무슨 일이에요? 먼저 timers 단계이고 그 다음에는 check 단계가 아닌가요? 어떻게 변할 수 있었나요? 🎜🎜사실 이벤트 루프에 들어갈 때 비동기 콜백이 완벽하게 준비되었는지 여부에 따라 다릅니다. 초기 예에서는 2000밀리초의 지연이 있으므로 이벤트 루프에 들어갈 때 setTimeout 콜백이 준비되어 있어야 합니다. 따라서 실행 순서는 변경되지 않습니다. 하지만 이 예에서는 메인 스레드에 실행할 동기화 코드가 없기 때문에 처음에 이벤트 루프에 들어갑니다. 그러나 이벤트 루프에 들어갈 때 setTimeout의 콜백이 반드시 완전히 준비되어 있지는 않습니다. 그래서 setImmediate 콜백 함수는 check 단계에서 먼저 실행되고 다음 이벤트 루프의 timers 단계에서 실행됩니다. code>setTimeout 콜백. 🎜🎜어떤 상황에서 setImmediate 콜백 함수가 동일한 지연 시간에 대해 setTimeout 콜백 함수보다 우선하게 됩니까? 🎜🎜실제로는 매우 간단합니다. 이 두 가지를 타이머 단계와 확인 단계 사이의 대기 콜백, 유휴, 준비, 폴 단계에 두기만 하면 됩니다. 어떤 무대든 괜찮아요. 왜냐하면 이러한 단계가 실행된 후에는 확실히 check로 이동한 다음 timers 단계로 이동하기 때문입니다. 🎜🎜 poll 단계를 예로 들어 이 두 가지를 IO 작업에 작성합니다. 🎜rrreee🎜또한 7번 실행합니다. 매번 setImmediate가 먼저 실행되는 것을 볼 수 있습니다. 🎜🎜노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해🎜🎜그래서 일반적으로 동일한 지연 시간으로 setTimeoutsetImmediate 이전에 100% 실행되지 않습니다. 🎜

      先微任务再宏任务

    主线程同步代码执行完毕后,会先执行微任务再执行宏任务。

    我们来看下面的例子

    console.log("start");
    
    setTimeout(() => {
      console.log("setTimeout");
    });
    
    setImmediate(() => {
      console.log("setImmediate");
    });
    
    Promise.resolve().then(() => {
      console.log("Promise.resolve");
    });
    
    console.log("end");

    我们运行一下看结果,可以看到它是先执行了微任务然后再执行宏任务

    노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

    nextTick优于其它微任务

    在微任务中nextTick的优先级是最高的。

    我们来看下面的例子

    console.log("start");
    
    setTimeout(() => {
      console.log("setTimeout");
    });
    
    setImmediate(() => {
      console.log("setImmediate");
    });
    
    Promise.resolve().then(() => {
      console.log("Promise.resolve");
    });
    
    process.nextTick(() => {
      console.log("process.nextTick");
    });
    
    console.log("end");

    我们运行上面的代码,可以看到就算nextTick定义在resolve后面,它也是先执行的。

    노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

    微任务穿插在各个阶段间执行

    怎么理解这个穿插呢?其实就是在事件循环的六个阶段每个阶段执行完后会清空微任务队列。

    我们来看例子,我们建立了timers、check、poll三个阶段,并且每个阶段都产生了微任务。

    // timers阶段
    setTimeout(() => {
      console.log("setTimeout");
    
      Promise.resolve().then(() => {
        console.log("setTimeout Promise.resolve");
      });
    });
    
    // check阶段
    setImmediate(() => {
      console.log("setImmediate");
      Promise.resolve().then(() => {
        console.log("setImmediate Promise.resolve");
      });
    });
    
    // 微任务
    Promise.resolve().then(() => {
      console.log("Promise.resolve");
    });
    
    // 微任务
    process.nextTick(() => {
      console.log("process.nextTick");
      Promise.resolve().then(() => {
        console.log("nextTick Promise.resolve");
      });
    });

    我们来执行上面的代码

    노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

    可以看到,先执行微任务,再执行宏任务。先process.nextTick -> Promise.resolve。并且如果微任务继续产生微任务则会再次清空,所以就又输出了nextTick Promise.resolve

    接下来到timer阶段,输出setTimeout,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setTimeout Promise.resolve

    接下来到check阶段,输出setImmediate,并且产生了一个微任务,再进入到下个阶段前需要清空微任务队列,所以继续输出setImmediate Promise.resolve

    这也就印证了微任务会穿插在各个阶段之间运行。

    노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해

    总结

    所以对于Node中的事件循环你只需要背好一以下几点就可以了

    • 当主线程同步代码执行完毕后才会进入事件循环

    • 事件循环总共分六个阶段,并且每个阶段都包括哪些回调需要记清楚。

    • 事件循环中会先执行微任务再执行宏任务。

    • 微任务会穿插在这六个阶段之间执行,每进入到下个阶段前会清空当前的微任务队列。

    • 微任务中process.nextTick的优先级最高,会优先执行。

    更多node相关知识,请访问:nodejs 教程

위 내용은 노드 이벤트 루프(EventLoop) 메커니즘에 대한 심층적인 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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