모든 노드 어플리케이션은 싱글스레드로 동작합니다. 즉 I/O와는 항상 분리되어 있고 노드의 이벤트 루프는 오직 하나의 작업과 이벤트만 처리한다는 의미입니다. 이벤트루프의 모든 Tick에서 Node가 처리하는 콜백을 큐에 추가하는 이벤트루프를 생각할 수 있습니다. 그래서 멀티코어상에서 노드를 운영하더라도 실제 처리에서 병렬구조(parallelism)의 어떤 이점도 얻을 수 없습니다. 모든 이벤트는 한번에 하나씩 처리됩니다. 그래서 노드는 I/O가 많은 작업에 적합하고 CPU 작업량이 많은 작업에는 적합하지 않습니다. 모든 I/O기반 작업에서 이벤트큐에 추가될 콜백을 쉽게 정의할 수 있습니다. 콜백은 I/O 작업이 완료되면 실행되고 동시에 어플리케이션은 다른 I/O 작업에 대한 요청을 계속해서 처리할 수 있습니다.
process.nextTick()
은 액션의 실행을 이벤트 루프의 다음 차례까지 실제로 연기합니다.
function foo() {
console.log('foo');
}
process.nextTick(foo);
console.log('bar');
setTimeout(foo, 0);
console.log('bar');
bar
foo
CPU 부하가 심한 작업의 실행을 다른 이벤트로 나누어 처리하기
CPU 작업량이 많은 계산을 계속해서 실행해야 하는 compute() 작업을 가정해보겠습니다. 같은 노드 프로세스에서 HTTP 요청의 처리같은 다른 이벤트도 처리해야 한다면 process.nextTick()을 사용해서 다른 이벤트를 처리하면서 compute()의 실행을 나누어서 처리할 수 있습니다.
var http = require('http');
function compute() {
// 복잡한 계산을 계속해서 수행
process.nextTick(compute);
}
http.createServer(function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World');
}).listen(5000, '127.0.0.1');
compute();
이 예제에서 compute()
를 재귀적으로 호출하는 대신 process.nextTick()
를 사용해서 이벤트루프의 다음 tick까지 compute()의 실행을 지연시켰습니다. 이렇게 함으로써 다른 HTTP요청
이 이벤트루프에 큐에 들어온다음 다음 compute()가 호출되기 전에 처리될 것입니다. process.nextTick()
를 사용하지 않고 그냥 compute()를 재귀적으로 호출했다면 프로그램은 들어오는 HTTP 요청을 처리할 수 없을 것입니다.
안타깝게도 process.nextTick()를 사용해도 멀티코어 병렬구조의 이득을 전혀 얻지 못합니다. 하지만 process.nextTick()을 사용하면 어플리케이션의 다른 부분에서 CPU를 공유해서 사용할 수 있습니다.
콜백을 받는 함수를 작성했을 때 이 콜백이 비동기로 실행됨을 항상 보장해야 합니다.
function asyncReal(data, callback) {
process.nextTick(function() {
callback(data === 'foo');
});
}