node.js 콜백함수 질문입니다.

조회수 914회

아래와 같은 코드를 실행했을 때, 제 생각으로는 맨 처음 Enter loop at ~이 출력되고 그 다음에 setTimeout에서 1초 뒤에 실행하라고 했으니까 Timeout ran at ~이게 출력 되고 4초 후에 Exit loop~ 이 순서로 출력이 되어야 할 것 같은데 실제 수행 순서는 Enter loop다음 Exit loop 다음 timeout ran at~ 이렇게 나옵니다. Timeoutran이 1초 뒤에 수행되는 순거니까 exit loop보다 먼저 출력되어야 하는 것 아닌가요?

// set function to be called after 1 second

setTimeout(function() {
    console.log('Timeout ran at ' + new Date().toTimeString());
}, 1000);

// store the start time

var start = new Date();
console.log('Enter loop at: '+ start.toTimeString());
// run a loop for 4 seconds
var loop = 0;
var end = start.getTime() + 4000;

    while( Date.now() < end) {
    loop++;
}

console.log('Exit loop at: ‘ + new Date().toTimeString() +
'. Ran ‘ + loop +' iterations.'); 

1 답변

  • 좋아요

    1

    싫어요
    채택 취소하기

    노드의 동작 방식을 알고 있어야 이해가 가능합니다.

    setTimeout은 다른 쓰레드에서 Timer를 돌리고 있다가 사용자가 지정한 시간(위 예제에서는 1초)에 이벤트를 발생시켜 이벤트 루프에 비동기 콜백을 전달합니다.

    'Timeout ran at ...'을 출력하는 콜백 말이죠.

    그렇다면 'Enter loop at: ...' 이 출력된 이후에 1초 뒤에 실행이 되어야 할 텐데 왜 그렇게 동작하지 않을까요? 그리고 출력 순서는 왜 저 모양 일까요?

    여기에는 두 가지 이유가 있습니다.

    1. 메인 쓰레드는 싱글 쓰레드

    노드의 메인 쓰레드는 싱글 쓰레드입니다. 그렇다는건 앞서 작성된 일련의 instruction이 끝나기 전까지 다른 작업들은 자기 차례가 오길 기다려야 하는 것이죠.

    위 예제에서 while 문이 4초 동안 loop 변수 값을 증가시키고 있습니다. 이 반복문이 끝나기 전까지 아래 작성된 코드들은 실행되지 못하고 자기 차례가 오기만을 하염없이 기다리고 있습니다. 비동기로 카운팅 되어 1초 이후 실행 되기로 한 timeout 콜백 또한 코드 흐름상 자기 차례가 오기까지 3초를 더 기다려야만 합니다. 일련의 작업이 한창 진행중 이므로 끼어들 틈이 없는거죠. 결국 총 4초 이후에야 밀려있던 'Timeout ran at ...' 출력 작업이 'Exit loop at: ...‘ 과 한꺼번에 출력 됩니다.

    한꺼번에 출력되는 건 뭐... 위와 같은 상황때문에 이해가 간다고 칩시다. 그렇다면 왜 'Timeout ran at ...'이 나중에 출력되는 걸까요?

    2. Event Callback이 동작하는 시점

    노드에서 I/O작업을 마친 후 어떤 일을 실행하라라고 비동기 작업에 대해 등록하는것이 이벤트 콜백입니다. I/O 비동기 작업(setTimeout 등)은 다른 쓰레드에서 병렬 실행되며 완료가 되면 Event Queue에 해당 콜백을 등록합니다. 이 Queue에 대기하고 있는 job들은 Event Loop가 메인 쓰레드가 유휴 상태인지를 확인하고 만약 쉬고 있다면 하나씩 꺼내어 실행시킵니다.

    메인쓰레드가 유휴 상태인지를 확인하기 위해서 Call Stack이 비어있는지를 확인합니다. Call Stack은 함수 호출 단위로 쌓입니다.

    (쿨럭... 그렇게 동작한다고 합니다...)

    위 예제의 전체 코드는 전역 context상에서 바로 실행됩니다. 이것도 하나의 함수 호출이라고 봐야죠. 그렇기 때문에 Call Stack에 등록됩니다. 이것을 main()이라고 칭해보죠.

    main() 함수는 'Exit loop at: ‘을 출력하는 부분이 함수의 마지막입니다. 이 마지막 줄까지 실행되어야 Call Stack에서 사라집니다. 때문에 'Exit loop at: ...‘가 출력되기 전까지 Event Loop는 메인 쓰레드가 아직 작업중이라고 판단합니다. 그래서 Queue에 있는 작업은 실행을 미루게 되죠.

    메인 함수가 실행되고나서 4초 뒤에 'Exit loop at: ...‘ 이 출력된 순간이 왔습니다. 그렇다는건 main()함수가 종료되는 시점이 왔다는 것이죠. Call Stack에서 main()함수는 사라집니다. 그제서야 Call Stack이 완전히 비워졌습니다. 감히 메인 쓰레드가 놀고 있기 때문에 Event Loop는 이를 가만히 두지 않습니다. 그래서 Event Queue에 밀려있던 작업을 메인 쓰레드에게 실행하라고 던져줍니다. 바로 이 작업이 메인 쓰레드가 바빠서 3초 동안 대기하고 있었던 'Timeout ran at ...'을 출력하는 콜백 입니다.

    그래서 결국...

    'Enter loop at: ...'

    'Exit loop at: ...‘

    'Timeout ran at ...'

    순서로 출력됩니다.


    동작 원리를 이해하기 전까지는 뭔가 직관적이지 않습니다만, 이러한 방식으로 노드는 싱글 쓰레드이면서 동시성 프로그래밍이 가능한 언어가 된 겁니다.

    그래서 혼자 일하는 주제에 빠릿하다는 느낌을 주고 있죠. 실제로도 V8성능 자체가 빠르기도 하고요. :)

    전에 제가 달았던 답변입니다. https://hashcode.co.kr/questions/6621/indexjs-%EC%95%88%EC%97%90-%EC%A0%81%EC%9D%80-%EC%BD%94%EB%93%9C%EB%93%A4%EC%9D%80-event-loop%EA%B0%80-%EB%8F%8C%EA%B8%B0-%EC%A0%84%EC%97%90-%EC%8B%A4%ED%96%89%EB%90%A9%EB%8B%88%EA%B9%8C

    이것 외에도 구글링하면 더 정확하고 좋은 설명들이 많습니다. 찾아보시면 도움이 될 거에요.

답변을 하려면 로그인이 필요합니다.

프로그래머스 커뮤니티는 개발자들을 위한 Q&A 서비스입니다. 로그인해야 답변을 작성하실 수 있습니다.

(ಠ_ಠ)
(ಠ‿ಠ)