nodejs의 이벤트 루프 메커니즘에 대한 자세한 설명

青灯夜游
풀어 주다: 2021-04-29 19:11:09
앞으로
2578명이 탐색했습니다.

이 글은 node의 이벤트 루프 메커니즘을 이해하는 데 도움이 됩니다. 도움이 필요한 친구들이 모두 참고할 수 있기를 바랍니다.

nodejs의 이벤트 루프 메커니즘에 대한 자세한 설명

프런트엔드 개발은 JavaScript와 분리될 수 없습니다. JavaScript는 웹 개발에 주로 사용되며 브라우저에서 구문 분석되고 실행되는 웹 프론트엔드 언어입니다. js의 역할은 프론트엔드 개발에만 국한되지 않고, 서버사이드 개발인 nodejs에도 사용될 수 있습니다. 이상과 야망을 가진 프런트엔드 사람으로서 시야를 넓히고 서버 측 개발 언어를 마스터하고 싶다면 nodejs가 매우 좋은 선택입니다.

관련 권장 사항: "nodejs Tutorial"

js 개발 방법을 마스터했으므로 노드를 시작하기 쉽고 npm 패키지 관리 도구도 개발 경험을 크게 향상시킵니다. Nodejs는 비동기 비차단 I/O 작업 방식으로 유명하며, 해당 처리 메커니즘을 이벤트 루프라고 합니다.

노드 이벤트 루프 메커니즘을 이해하면 노드의 이벤트 처리 방법과 비동기 이벤트의 실행 타이밍을 더 잘 이해할 수 있습니다. 이 기사에서는 주로 nodejs의 이벤트 루프 메커니즘을 설명하고 노드 후속 학습의 기초를 마련합니다.

1. node VS javascript

앞서 언급한 것처럼 Javascript는 주로 웹 개발에 사용되는 웹 프런트엔드 언어로, 브라우저에서 구문 분석하고 실행하는 반면 node.js는 이를 기반으로 하는 JavaScript 실행 환경입니다. Chrome V8 엔진입니다. 따라서 nodejs는 언어, 라이브러리, 프레임워크가 아니라 js 런타임 환경입니다. 간단히 말해서 node는 js 코드를 구문 분석하고 실행할 수 있습니다. 과거에는 브라우저에서만 JS를 구문 분석하고 실행할 수 있었지만 이제는 브라우저 없이도 노드를 사용하여 JS를 완전히 실행할 수 있습니다.

node.js와 브라우저 js에는 많은 차이점이 있습니다. 예를 들어 브라우저의 js에는 ecmascript, BOM, DOM이 포함되어 있지만 nodejs의 js에는 BOM과 DOM이 없고 emcscript만 있습니다. 그리고 노드의 js 실행 환경은 파일 읽기 및 쓰기, 네트워크 서비스 구축, 네트워크 통신, http 서버 등과 같은 js용 서버 수준 작업 API를 제공합니다. 이러한 API의 대부분은 핵심 모듈에 패키지되어 있습니다. 또한 노드의 이벤트 루프 메커니즘은 브라우저 js의 이벤트 루프 메커니즘과 다릅니다.

2. JavaScript 이벤트 루프

모두가 이미 브라우저의 js 이벤트 루프에 대해 매우 명확하게 알고 있습니다. 비교를 위해 여기서 간단히 언급하겠습니다.

nodejs의 이벤트 루프 메커니즘에 대한 자세한 설명

(필립 로버츠의 연설 "도와주세요, 이벤트 루프에 갇혀 있어요"에서 인용)

js가 실행되면 동기 작업과 비동기 작업은 각각 다른 실행 환경에 들어갑니다. 동기 작업은 메인 스레드, 즉 메인 실행 스택과 비동기 작업(ajax 요청, settimeout, setinterval, poromise.resolve() 등)에 들어갑니다. ) 작업 대기열을 입력합니다. 다양한 비동기 작업은 ajax 요청, settimeout, setinterval 등과 같은 다양한 작업 대기열로 푸시됩니다. 이러한 작업은 매크로 작업 대기열(매크로 작업)로 푸시되고 Promise 함수는 마이크로 작업 대기열( 마이크로 태스크). 전체적인 이벤트 루프 프로세스는 다음과 같습니다.

  • 동기 코드가 실행되면 기본 실행 스택이 비워지고 준비 작업이 비동기 작업을 실행하기 시작합니다.

  • 메인 스레드는 마이크로태스크 대기열이 비어 있는지 확인합니다. 비어 있지 않으면 대기열의 모든 마이크로태스크를 순회하여 실행하고 마이크로태스크 대기열을 지운 다음 매크로태스크 대기열을 확인합니다. 마이크로태스크 대기열이 비어 있으면 다음 단계로 바로 진행하세요.

  • 메인 스레드는 매크로 작업 대기열을 순회하여 매크로 작업 대기열의 첫 번째 매크로 작업을 실행합니다. 실행 중에 매크로 작업이나 마이크로 작업을 발견하면 계속해서 해당 작업 대기열로 푸시합니다. 매크로 작업이 실행되면 마이크로 작업 대기열을 순회하고 비워야 합니다

  • 렌더링 작업을 수행하고 뷰를 업데이트합니다

  • 다음 이벤트 루프를 시작하고 두 작업 대기열이 지워질 때까지 위 단계를 반복합니다

영향을 더욱 심화시키기 위해 작은 예를 들어 다음 코드가 무엇을 출력하는지 살펴보겠습니다.

    var le=Promise.resolve(2);
    console.log(le)
    console.log('3')
    Promise.resolve().then(()=>{
    console.log('Promise1')  
    setTimeout(()=>{
        console.log('setTimeout2')
    },0)
    })
    setTimeout(()=>{
    console.log('setTimeout1')
    Promise.resolve().then(()=>{
        console.log('Promise2')    
    })
    },0);
로그인 후 복사

위의 이벤트 루프 프로세스를 사용하여 분석합니다.

  • js 기본 프로세스 실행 코드에서 Promise.resolve(2 ) , 즉시 실행되어 2를 promise 개체로 바꾼 다음 console.log(le)가 le 변수를 인쇄합니다. ----->Promise {:
  • console .log( '3'), print ----->3
  • 그런 다음 실행을 계속하다가 마이크로태스크 스택으로 푸시된 비동기 마이크로태스크 함수인 Promise.resolve()를 만나게 됩니다
  • 다음 함수는 다음과 같습니다. setTimeout, 매크로 작업 대기열에 푸시하면 이제 메인 프로세스가 비어 있습니다
  • 마이크로 작업 대기열을 확인하고 Promise.resolve().를 찾은 다음 ----->promise1을 인쇄하고 다른 타이머를 만나 푸시합니다. 작업 대기열 끝에서 마이크로 작업 대기열은 비어 있습니다
  • 매크로 작업 대기열을 확인하고 실행할 첫 번째 매크로 작업을 가져오고 ----->setTimeout1을 인쇄하고 Promise.resolve()를 만나고 다시 푸시합니다. to microtask 대기열
  • 은 다음 매크로 작업을 시작하기 전에 확실히 마이크로 작업을 지울 것입니다. 따라서 setTimeout1을 인쇄한 후 마이크로 작업 대기열이 확인되므로 --->promise2
  • 이벤트 루프의 다음 라운드가 매크로 작업 대기열을 차지합니다. 현재 첫 번째 작업이 실행되었으므로 print------>setTimeout2가 인쇄됩니다. 이 시점에서 매크로 작업과 마이크로 작업 대기열이 모두 지워지고 이벤트 루프가 종료됩니다.

그래서 출력 결과는 다음과 같습니다. : 약속 {: 2 }, 3, promise1, setTimeout1, promise2, setTimeout2.

브라우저에서의 실행 결과는 다음과 같습니다.

nodejs의 이벤트 루프 메커니즘에 대한 자세한 설명

3. 노드 이벤트 루프

노드의 이벤트 루프는 6단계로 이루어지며, 이 6단계는 다음 노드까지 순차적으로 실행됩니다. 이벤트 처리가 완료되었습니다. 6단계의 시퀀스 다이어그램은 다음과 같습니다.

nodejs의 이벤트 루프 메커니즘에 대한 자세한 설명

六个阶段分别是:

  • timers 阶段:这个阶段执行timer(setTimeout、setInterval)的回调
  • I/O callbacks 阶段:执行一些系统操作的回调(比如网络通信的错误回调);
  • idle, prepare 阶段:仅node内部使用,可忽略
  • poll 阶段:获取新的I/O事件, 适当的条件下node将阻塞在这里
  • check 阶段:执行 setImmediate() 的回调
  • close callbacks 阶段:执行 socket 的 close 事件回调,如果一个socket或handle被突然关掉(比如socket.destroy()),close事件将在这个阶段被触发

事件循环中,每当进入某一个阶段,都会从该阶段对应的回调队列中取出函数去执行。当队列为空或者执行的回调函数数量到达系统设定的阈值,该阶段就会终止,然后检查NextTick队列和微任务队列,将其清空,之后进入下一个阶段。

这里面比较关键的是poll阶段:

  • poll队列不为空的时候,事件循环会遍历队列并同步执行回调,直到队列清空或执行回调数达到系统上限。
  • poll队列为空的时候,就会有两种情况:
    • 如果代码中存在setImmediate()回调,那么事件循环直接结束poll阶段进入check阶段来执行check队列里的回调;
    • 如果不存在setImmediate()回调,会等待回调被加入到队列中并立即执行回调,这里同样会有个超时时间设置防止一直等待下去,如果规定时间内有定时器函数进入队列,则返回到timer阶段,执行定时器回调,否则在poll阶段等待回调进入队列。

同样的举个大大的,看看以下代码会输出什么:

console.log('start')
setTimeout(() => {
  console.log('timer1')
  Promise.resolve().then(function() {
    console.log('promise1')
  })
}, 0)
setTimeout(() => {
  console.log('timer2')
  Promise.resolve().then(function() {
    console.log('promise2')
  })
}, 0)
Promise.resolve().then(function() {
  console.log('promise3')
})
console.log('end')
로그인 후 복사

利用node事件循环分析呗:

  • 先执行同步任务,打印start,end
  • 进入timer阶段前,清空NextTick和micro队列,所以打印promise3
  • 进入timer阶段,打印timer1,并发现有一个微任务,立即执行微任务,打印promise1
  • 仍然在timer阶段,执行下个宏任务,打印timer2,同样遇到微任务,立即执行,打印promise2

因此输出顺序是:start,end,promise3,timer1,promise1,timer2,promise2,如果能正确回答出来说明对node的循环机制有了大体的了解,实际node输出结果确实是这样:

nodejs의 이벤트 루프 메커니즘에 대한 자세한 설명

那如下代码会输出什么呢?

process.nextTick(function(){
    console.log(7);
});

new Promise(function(resolve){
    console.log(3);
    resolve();
    console.log(4);
}).then(function(){
    console.log(5);
});

process.nextTick(function(){
    console.log(8);
});
로그인 후 복사

继续分析:

  • process.nextTick会将任务推进至nextTick队列,promise.then会把任务推至micro队列,上面提到过每次一个宏任务执行完,执行下一个宏任务之前需要清空nextTick队列和micro队列,同样的一个阶段执行完,进入下一个阶段之前也需要nextTick队列和micro队列,并且nextTick队列优先级高于micro队列
  • 先执行同步代码,打印3,4
  • 执行nextTick队列,打印7,8
  • 再执行micro队列,打印5

因此最终输出是:3,4,7,8,5,需要记住,process.nextTick 永远大于 promise.then的优先级

还有一个大家很容易混淆的点就是setTimout和setImmediate的执行时机,根据上面描述的node事件循环机制,setImmediate()应该在check阶段执行 与 而setTimeout在timer阶段执行,理论上setTimout比setImmediate先执行,看下面的代码:

setTimeout(() => console.log(1),0);
setImmediate(() => console.log(2));
로그인 후 복사

执行结果是什么?1,2 还是 2,1,其实都有可能,看实际node运行的结果:

nodejs의 이벤트 루프 메커니즘에 대한 자세한 설명

可以看到两次执行的结果不一样,为什么呢?原因在于即使setTimeout的第二个参数默认为0,但实际上,Node做不到0秒就执行其回调,最少也要4毫秒。那么进入事件循环后,如果没到4毫秒,那么timers阶段就会被跳过,从而进入check阶段执行setImmediate回调,此时输出结果是:2,1;

如果进入事件循环后,超过4毫秒(只是个大概,具体值并不确定),setTimeout的回调会出现在timer阶段的队列里,回调将被执行,之后再进入poll阶段和check阶段,此时输出结果是:1,2

那如果两者在I/O周期内调用,谁先执行呢?看一下代码:

const fs = require('fs')

fs.readFile('./test.txt', 'utf8' , (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  setTimeout(() => {
    console.log('timeout');
  }, 0);
  setImmediate(() => {
    console.log('immediate');
  });
})
로그인 후 복사

实际上,node中输出的结果总是immediate先输出,timeout后输出。因为I/O回调是在poll阶段执行,当回调执行完毕之后队列为空,发现存在setImmediate的回调就会进入check阶段,执行完毕后,再进入timer阶段。

四、总结

本文结合代码示例,对node的事件循环机制做了比较详细描述。通过这篇文章,应该可以了解浏览器的事件循环机制是怎样的,node的循环机制是怎样的,以及nextTick和micro队列的优先级,setTimout和setImmediate执行时机等一些容易混淆的知识点。文章中不足和不对之处,欢迎在评论区交流讨论,一起探索,谢谢。

更多编程相关知识,请访问:编程入门!!

위 내용은 nodejs의 이벤트 루프 메커니즘에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:juejin.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿