>  기사  >  웹 프론트엔드  >  Node.js의 타이머 이해

Node.js의 타이머 이해

青灯夜游
青灯夜游앞으로
2020-12-03 18:11:133152검색

Node.js의 타이머 이해

관련 추천: "node js tutorial"

timer는 미래의 특정 시점에 호출될 함수를 예약하는 데 사용됩니다. Node.js의 타이머 함수는 Node.js에서 제공하는 타이머 API를 구현합니다. 웹 브라우저 유사한 API이지만 이벤트 루프 구현을 사용하면 Node.js에 4개의 관련 메소드가 있습니다

  • setTimeout(callback, Delay[, ...args])

  • setInterval(callback[, ... args ])

  • setImmediate(callback[, ...args])

  • process.nextTick(callback[, ...args])

처음 두 가지 의미는 웹의 의미와 일치합니다. 마지막 두 가지는 Node.js에 고유한 것으로, 효과는 setTimeout(callback, 0)인 것 같습니다. 이는 Node.js 프로그래밍에서 가장 많이 사용됩니다

Node.js는 콜백이 트리거되는 정확한 시간을 보장하지 않으며, 그렇지도 않습니다. 순서가 보장되면 콜백은 지정된 시간에 최대한 가깝게 호출됩니다. setTimeout 지연이 2147483647보다 크거나 1보다 작으면 지연은 1로 설정되고 정수가 아닌 지연은 정수로 잘립니다.

이상한 실행 순서

몇 가지 방법을 사용하여 비동기적으로 인쇄하는 예를 살펴보세요. number

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
console.log(5);

는 5 4 3 2 1 또는 5 4 3 1 2

동기식 및 비동기식

을 인쇄합니다. 다섯 번째 줄은 동기식 실행이고 나머지는 비동기식입니다

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);

그러므로 5를 먼저 인쇄하면 이해하기 쉽습니다. 나머지는 모두 비동기 작업입니다. Node.js는 어떤 순서로 작업을 실행하나요?

event loop

Node.js는 시작 후 이벤트 폴링을 초기화합니다. 그 과정에서 비동기 호출, 타이머 예약 및 process.nextTick()을 처리한 다음 이벤트 루프 처리를 시작할 수 있습니다. 공식 홈페이지에 이벤트 루프 동작 순서를 소개하는 그림이 있습니다

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

이벤트 루프의 각 단계에는 작업 대기열이 있습니다. 이벤트 루프가 특정 단계에 진입하면 해당 단계의 작업 대기열이 다음 단계까지 실행됩니다. 또는 실행된 콜백이 시스템 상한에 도달하면 다음 단계로 이동합니다. 모든 단계가 순차적으로 실행되면 이벤트 루프가 틱을 완료했다고 합니다

비동기 작업은 다음 단계에 배치됩니다. 이벤트 루프 틱, 프로세스.nextTick은 다음 이벤트 루프 틱에 들어가기 전에 실행되므로 다른 비동기 작업보다 먼저 실행되어야 합니다

setImmediate(console.log, 1);
setTimeout(console.log, 1, 2);
Promise.resolve(3).then(console.log);
/****************** 下次 event loop tick 分割线 ********************/
process.nextTick(console.log, 4);
/****************** 同步任务和异步任务的分割线 ********************/
console.log(5);

각 단계의 주요 작업

  • timers: setTimeout, setInterval 콜백 실행

  • pending callbacks: 실행 I/O(파일, 네트워크 등) callback

  • idle, prepare: 시스템 내부에서만 호출

  • poll: 새 I/O 이벤트 가져오기, 실행 관련 콜백을 처리하고 적절한 조건에서 노드를 차단합니다.

  • check: 이 단계에서 setImmediate 콜백이 실행됩니다.

  • close callbacks: 소켓 등의 종료 이벤트 콜백을 실행합니다.

대부분 일일 개발의 비동기 작업은 타이머, 폴링, 단계 처리 확인에서 수행됩니다.

timers

Node.js는 타이머 단계에 만료된 타이머가 있는지 확인합니다. 만료된 타이머가 있는 경우 콜백은 다음과 같습니다. 실행을 기다리기 위해 타이머 대기열에 배치됩니다. Node.js는 단일 스레드를 사용하며 시스템의 상황 및 기타 프로세스에 따라 타이머가 실행된다는 보장이 없습니다. 정확한 시간에는 두 가지 주요 유형의 타이머가 있습니다.

  • Immediate

  • Timeout

Immediate 유형 타이머 콜백은

check 단계에서 호출되며, Timeout 타이머는 즉시 콜백을 호출합니다. 설정된 시간이 지난 후에는 가능하지만 여러 번 실행하면 I/O를 차단하고 폴링하는 시간이 달라지는 것을 알 수 있습니다


그러면 폴 큐에 있는 이벤트를 처리합니다

이벤트 루프가 폴 단계에 진입하고 예약된 타이머가 없는 경우

  • 폴 큐가 비어 있지 않으면 이벤트 루프는 콜백 큐를 반복하고 큐가 소진되거나 시스템에 도달할 때까지 동기적으로 실행됩니다. 최대 콜백 수에 도달

    폴 큐가 비어 있는 경우
  • setImmediate() 작업이 있는 경우 이벤트 루프는 폴

    단계에서 종료됩니다.

    check
  • 단계에 들어간 후

setImmediate( ) 작업, 이벤트 루프는 콜백이 대기열에 추가되기를 기다리는

poll
    단계에서 차단된 다음 즉시 실행됩니다
    • 일단 poll 대기열이 비어 있으면 이벤트 루프는 타이머 큐가 비어 있으면 이벤트 루프의 다음 라운드로 들어갑니다위에서 언급한 것처럼 서로 다른 I/O에 있으면 setTimeout과 setImmediate의 실행 순서를 결정할 수 없지만 setTimeout과 setImmediate가 하나의 I/O에 있으면 실행 순서를 결정할 수 없습니다. /O 콜백에서는 setImmediate() 작업이 poll 단계에서 감지되고, 이벤트 루프가 직접 check 단계에 들어가 setImmediate 콜백을 실행하기 때문에 setImmediate가 먼저 실행되어야 합니다.
    • setTimeout(() => {
        console.log('timeout');
      }, 0);
      
      setImmediate(() => {
        console.log('immediate');
      });
    • checksetImmediate 콜백이 실행됩니다. 이 단계에서는
    • 为什么 Promise.then 比 setTimeout 早一些

      前端同学肯定都听说过 micoTask 和 macroTask,Promise.then 属于 microTask,在浏览器环境下 microTask 任务会在每个 macroTask 执行最末端调用

      在 Node.js 环境下 microTask 会在每个阶段完成之间调用,也就是每个阶段执行最后都会执行一下 microTask 队列

      setImmediate(console.log, 1);
      setTimeout(console.log, 1, 2);
      /****************** microTask 分割线 ********************/
      Promise.resolve(3).then(console.log); // microTask 分割线
      /****************** 下次 event loop tick 分割线 ********************/
      process.nextTick(console.log, 4);
      /****************** 同步任务和异步任务的分割线 ********************/
      console.log(5);

      setImmediate VS process.nextTick

      setImmediate 听起来是立即执行,process.nextTick 听起来是下一个时钟执行,为什么效果是反过来的?这就要从那段不堪回首的历史讲起

      最开始的时候只有 process.nextTick 方法,没有 setImmediate 方法,通过上面的分析可以看出来任何时候调用 process.nextTick(),nextTick 会在 event loop 之前执行,直到 nextTick 队列被清空才会进入到下一 event loop,如果出现 process.nextTick 的递归调用程序没有被正确结束,那么 IO 的回调将没有机会被执行

      const fs = require('fs');
      
      fs.readFile('a.txt', (err, data) => {
      	console.log('read file task done!');
      });
      
      let i = 0;
      function test(){
      	if(i++ < 999999) {
        	console.log(`process.nextTick ${i}`);
          process.nextTick(test);
        }
      }
      test();

      执行程序将返回

      nextTick 1
      nextTick 2
      ...
      ...
      nextTick 999999
      read file task done!

      于是乎需要一个不这么 bug 的调用,setImmediate 方法出现了,比较令人费解的是在 process.nextTick 起错名字的情况下,setImmediate 也用了一个错误的名字以示区分。。。

      那么是不是编程中应该杜绝使用 process.nextTick 呢?官方推荐大部分时候应该使用 setImmediate,同时对 process.nextTick 的最大调用堆栈做了限制,但 process.nextTick 的调用机制确实也能为我们解决一些棘手的问题

      • 允许用户在 even tloop 开始之前 处理异常、执行清理任务

      • 允许回调在调用栈 unwind 之后,下次 event loop 开始之前执行

      一个类继承了 EventEmitter,而且想在实例化的时候触发一个事件

      const EventEmitter = require(&#39;events&#39;);
      const util = require(&#39;util&#39;);
      
      function MyEmitter() {
        EventEmitter.call(this);
        this.emit(&#39;event&#39;);
      }
      util.inherits(MyEmitter, EventEmitter);
      
      const myEmitter = new MyEmitter();
      myEmitter.on(&#39;event&#39;, () => {
        console.log('an event occurred!');
      });

      在构造函数执行 this.emit('event') 会导致事件触发比事件回调函数绑定早,使用 process.nextTick 可以轻松实现预期效果

      const EventEmitter = require('events');
      const util = require('util');
      
      function MyEmitter() {
        EventEmitter.call(this);
      
        // use nextTick to emit the event once a handler is assigned
        process.nextTick(() => {
          this.emit('event');
        });
      }
      util.inherits(MyEmitter, EventEmitter);
      
      const myEmitter = new MyEmitter();
      myEmitter.on('event', () => {
        console.log('an event occurred!');
      });

      更多编程相关知识,请访问:编程教学!!

      위 내용은 Node.js의 타이머 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

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