이 기사는 Node.js의 이벤트 루프(타임 루프) 메커니즘을 이해하는 데 도움이 되기를 바랍니다.
오늘은 nodeJs의 이벤트 루프에 대해 알아 보겠습니다. 저에게는 항상 이벤트 루프에 대한 이해가 큰 어려움이었습니다. 이번 연구를 통해 이 어려움을 극복하고 또한 이 블로그를 통해 이벤트 루프에 대한 이해와 감동이 깊어졌으면 좋겠습니다.
이벤트 루프를 배우기 전에 먼저 노드의 libuv를 이해하세요. libuv는 다양한 운영 체제에서 다양한 I/O 모델의 구현을 담당하고 타사 애플리케이션과 함께 사용할 수 있는 API로 다양한 구현을 추상화합니다.
이벤트루프를 정식으로 배우기에 앞서, 한 가지 질문을 생각해 봅시다
setTimeout(() => { console.log("timer1"); Promise.resolve().then(() => { console.log("promise1"); }); }, 0); setTimeout(() => { console.log("timer2"); Promise.resolve().then(() => { console.log("promise2"); }); }, 0);
이 코드를 브라우저에서 실행하면 어떤 결과가 나올까요?
노드에서 실행한 결과는 무엇인가요? ㅋㅋㅋ
먼저 사진을 보겠습니다.
사진에서 타이머, 보류 중인 콜백, 유휴/준비, 폴링, 확인, 콜백 닫기의 6단계를 볼 수 있습니다.
timers 단계: 주로 setTimeOut, setInterval 콜백 실행
pending 콜백 단계: 네트워크 통신 오류 콜백과 같은 일부 시스템 호출 오류 실행
폴링 단계: 파일 읽기를 위한 I/O 콜백 가져오기와 같은 새로운 I/O 이벤트를 가져옵니다.
적절한 상황에서 nodejs는 이 단계에서 차단됩니다check 단계: setImmediate 콜백 실행First in First out
) 규칙에 따라 작업 대기열에 있는 작업을 실행합니다. 이 6가지 단계 중timers
먼저 setTimeOut을 선언한 다음 외부에서 파일을 읽습니다. 파일 읽기 작업이 타이머를 초과하면 파일 읽기 작업이 서버의 콜백을 설정합니다. 앞서 언급한 것처럼 메인 스레드가 유휴 상태가 아닌 상황입니다.
폴 단계는 주로 두 가지 작업을 수행합니다. 1. 폴 단계의 작업 대기열을 처리합니다. 2 타이머가 초과되면 콜백 기능을 실행합니다.
폴링 단계에서 폴링 작업 대기열의 작업을 실행한 후 사전 설정된 setImmediate가 있는지 확인하고, 없으면 확인 단계로 들어갑니다. 여기서 차단하겠습니다.
여기서 질문이 있습니다. 투표 단계에서 차단되면 우리가 설정한 타이머가 실행될 수 없는 것 아닌가요?
사실
이벤트 루프가 폴링 단계에서 차단되면 nodejs에는 타이머 큐가 비어 있는지 확인하는 메커니즘이 있습니다. 비어 있지 않으면 타이머 단계로 다시 들어갑니다.
event-loop的每个阶段都有一个队列,当event-loop达到某个阶段之后,将执行这个阶段的任务队列,直到队列清空或者达到系统规定的最大回调限制之后,才会进入下一个阶段。当所有阶段都执行完成一次之后,称event-loop完成一个tick。
上面我们说完了event-loop的理论部分,但是光有理论我们也还是不能很清晰的理解event-loop。下面我们就根据几个demo来更加深入的理解下event-loop!
demo1
const fs=require('fs') fs.readFile('test.txt',()=>{ console.log('readFile') setTimeout(()=>{ console.log('settimeout'); },0) setImmediate(()=>{ console.log('setImmediate') }) })
执行结果:
可见执行结果跟我们前面的分析时一致的!
demo2
const fs = require("fs"); const EventEmitter = require("events").EventEmitter; let pos = 0; const messenger = new EventEmitter(); messenger.on("message", function (msg) { console.log(++pos + " message:" + msg); // }); console.log(++pos + " first"); // process.nextTick(function () { console.log(++pos + " nextTick"); // }); messenger.emit("message", "hello!"); fs.stat(__filename, function () { console.log(++pos + " stat"); // }); setTimeout(function () { console.log(++pos + " quick timer"); // }, 0); setTimeout(function () { console.log(++pos + " long timer"); // }, 30); setImmediate(function () { console.log(++pos + " immediate"); // }); console.log(++pos + " last"); //
结果:
在node 8.6 之前:
浏览器中的微任务队列会在每个宏任务执行完成之后执行,而node中的微任务会在事件循环的各个阶段之间执行,即每个阶段执行完成之后会去执行微任务队列。
在8.6之后:
浏览器和node中微任务的执行是一致的!
所以,在文章开头,我们提出的思考的问题就有了结果。
语法:process.nextTick(callback,agrs)
执行时机:
这个函数其实是独立于 Event Loop 之外的,它有一个自己的队列,当每个阶段完成后,如果存在 nextTick 队列,就会清空队列中的所有回调函数,并且优先于其他 microtask 执行。递归的调用process.nextTick()
会导致I/O starving,官方推荐使用setImmediate()
关于starving现象的说明:
const fs = require("fs"); fs.readFile("test.txt", (err, msg) => { console.log("readFile"); }); let index = 0; function handler() { if (index >= 30) return; index++; console.log("nextTick" + index); process.nextTick(handler); } handler();
运行结果:
可以看到,等到nextTick函数呗执行30次之后,读取文件的回调才被执行!这样的现象被称为 I/O 饥饿。
当我们把 process.nextTick 换为 setImmediate
const fs = require("fs"); fs.readFile("test.txt", (err, msg) => { console.log("readFile"); }); let index = 0; function handler() { if (index >= 30) return; index++; console.log("nextTick" + index); setImmediate(handler); } handler();
结果:
造成这两种差异的原因是,嵌套调用的setImmediate的回调被排到了下一次event-loop中去!
通过今天的学习,让我对event-loop的理解更深刻了。那么,下次见。好好学习,天天向上!
更多编程相关知识,请访问:编程视频!!
위 내용은 Node.js의 이벤트 루프 메커니즘에 대해 설명하는 기사의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!