[NodeJS] Event-Loop Part 2 : setTimeout() vs setImmediate() vs process.nextTick()

들어가기에 앞서

  1. 이전글 : Event-Loop Part 1 : Big Picture
  2. 이 글은 setTimeout(), setImmediate(), process.nextTick() 으로 설정한 callback function이 어떤 순서로 실행되는지에 대한 설명입니다.
  3. 추가적인 그림

이전에 위와 같은 그림을 그렸었는데, 이전글과 이번글을 쓰는데 상당히 많이 참조한 이 글에 나오는 아래 그림을 바탕으로 설명하겠습니다(소스코드도 많이 가져다 썼습니다).

from : https://jsblog.insiderattack.net/timers-immediates-and-process-nexttick-nodejs-event-loop-part-2-2c53fd511bb3

4. 이 글에서 setTimeout()는 setTimeout 함수 자체를 의미하고, setTimeout(cb)는 setTimeout()에서 설정한 callback function을 의미합니다.

심플한 결론

  1. setTimeout(cb, 0)이 setImmediate(cb)보다 먼저 실행된다
  2. process.nextTick(cb)은 next tick queue에 들어가며, 각 phase사이사이에서 호출된다
  3. process.nextTick(cb)은 timer phase(timer queue)이후부터 호출될것 같지만, 실제로는 먼저 호출된다
  4. 순서는 예상과 다르게 호출 될 수 있다

설명

setTimeout(cb, 0)은 0초후에 실행되지 않는다

setTimeout(cb)은 timer queue에 들어가있을테지만, loop가 딱 1초가 지나는 순간에 timer queue에서 딱 1초가 지났는지 판별해서 cb을 호출하기는 어렵기 때문이다(확률이 매우 낮을것으로 예상). 저 1000은 단지, 최소한 1초안에는 호출되지 않는다는걸 보장할 뿐이다.

setTimeout() vs setImmediate()

  1. setTimeout()이 실행되고 cb이 timer queue에 들어간다
  2. setImmediate()가 실행되고 cb이 immediate queue에 들어간다
  3. 루프를 돌리기 시작하고, timer queue에 있는 cb은 아직 1초가 지나지 않았기 때문에 실행되지 않는다
  4. 다른 phase를 지나 immediate queue에 들어있는 cb펑션을 호출한다
  5. 한바퀴 돌았으니까 이제 다시 timer queue를 돌고 이제, 첨에 등록해놨던 setTimeout(cb)이 실행된다
  6. output이 항상 같다

이번엔 setTimeout(cb)가 0초후에 실행되도록 해보았다

  1. setTimeout()이 실행되고 cb가 timer queue에 들어간다
  2. setImmediate()가 실행되고 cb가 immediate queue에 들어간다
  3. 루프를 돌리기 시작하고, timer queue에 있는 cb펑션은 0초가 지났기 때문에 호출된다
  4. 다른 phase를 지나 immediate queue에 들어있는 cb펑션을 호출한다
  5. output이 항상 일정하게 출력되지는 않는다. 왜냐하면 0초후에 실행하라고 명령해도 내부적으로는 1ms로 설정된다고 한다. 따라서 시스템상 1ms가 안지났다고 판단해서, 해당 cb이 호출되지 않고 다음 phase로 넘어갈 수 있기 때문이다.

setTimeout() vs setImmediate() vs process.nextTick()

  1. setImmediate(cb)가 immediate queue에 들어간다
  2. setTimeout(cb) 가 timer queue에 들어간다
  3. process.nextTick(cb) 가 next tick queue에 들어간다
  4. 루프가 timer phase로 가기전에 next tick queue를 먼저 돈다
  5. 두번째 process.nextTick(cb)이 호출될때 안에서 한번더 process.nextTick()이 호출되었기 때문에, next tick queue 에 하나가 더 쌓인다. 이거는 네번째 process.nextTick(cb)이 호출되고 나서 호출된다(=> this is the inner next tick inside next tick)
  6. 이제 next tick queue가 비었으므로, timer queue를 돌기 시작한다
  7. 모든 setTimeout(cb)이 0초후에 실행되도록 설정되었고, next tick queue를 도는동안 최소 1ms는 지났기 때문에 timer queue에 있는 모든 cb을 호출한다
  8. 두번째 setTimeout(cb)이 호출될때 process.nextTick()을 호출했기 때문에, next tick queue에 cb이 하나 들어가진다
  9. timer queue를 다 돌고 i/o event queue 에 들어가기에 앞서 next tick queue를 한번 검사한다. 안에 방금전 두번째 setTimeout()에서 등록한 cb이 있기 때문에 그걸 호출한다( => this is process.nextTick added inside setTimeout)
  10. i/o event queue에 pending cb도 없고, i/o polling할것도 없기 때문에 immediate queue로 넘어간다. 코드의 맨 처음 설정한 setImmediate(cb)부터 하나씩 호출한다

I/O 첨가

  1. readFile()이 실행되고 다른 일이 없으니까 main thread는 hello.txt가 읽히기를 기다리고(i/o polling) 다 읽혀지면, cb을 호출한다
  2. timer queue에 하나 담기고, immediate queue에 하나 담긴다
  3. timer phase -> i/o polling -> immediate phase 순서기 때문에, 언제나 setImmediate(cb)가 먼저 호출된다. 다시말해서 전체적으로는 timer queue에 도 cb이 있고 immediate queue에도 cb이 있지만, i/o polling다음에 살펴볼 queue가 immediate기 때문에 setImmediate(cb)가 호출되는것이다.

참고 자료 리스트

  1. Timers, Immediates and Process.nextTick — NodeJS Event Loop Part 2

마지막으로

  1. 잘못된 내용이 있으면 언제든지 알려주시기 바랍니다.
  2. 질문이 있으면 댓글에 달아주시기 바랍니다. 아는만큼 답변하겠습니다.

읽어주셔서 감사합니다.