My Profile Photo

DongChanS's blog


수학과 학생의 개발일지


자바스크립트 (4) - Non-block function이란?

1) 다소 특이한 함수

자바스크립트는 특이한 형태의 함수가 있습니다.

그 중 하나가 setTimeout인데요.

1-1. setTimeout

이 함수는 특정 시간 이후에 함수를 실행시켜주는 기능을 합니다.

setTimeout(function, milliseconds)

  • function : 타이머가 만료된 뒤 실행되는 함수
  • milliseconds : 타이머의 시간 ( 밀리세컨드 단위 )

그런데 이 함수는 약간 특이합니다.

실행되는 순서가 다소 이상합니다.

const timeout = (sec) => {
    setTimeout(() => {
        console.log("두번째")
    }, sec * 1000)
}

console.log("첫번째")
timeout(3)
console.log("세번째")

상식적으로 정상적인 함수의 흐름을 따른다면, 첫번째 -> 두번째 -> 세번째 순으로 출력되어야 정상이지만,

1

실제로는 세번째가 먼저 출력이 됩니다.

이런 이상한 함수들을 non-block function이라고 합니다.

2) Non-block function

Non-block function에 대해서 설명하기 전에 block function이 뭔지 알아보도록 합시다.

2-1. Block function

function sleep(sec) {
    const future = Date.now() + sec * 1000
    while (Date.now() < future) {}
    console.log("두번째")
}

일반적인 함수나 for문같은 제어문들은 block({})으로 둘러싸여 있습니다.

block 앞의 명령이 시행되고 있는 중에는, 그 뒤의 명령이 실행되지 못하도록 막는 역할을 하는데요.

이 함수는 위의 setTimeout과 동일하게 sec초가 지난 뒤에 출력을 해주는 역할이지만, 정상적인 block function이기 때문에 위와는 다르게 작동합니다.

2

console.log(“두번째”) 가 출력되기 전까지는 sleep() 바깥으로 새어나지 못하므로 세번째가 나중에 출력되는 것입니다.

2-2. Thread, Process

그렇습니다. Non-block function은 block이 없기 때문에 setTimeout이 끝나기 이전에 console.log(“세번째”)가 먼저 실행이 되는 것이고,

setTimeout을 어딘가에서 기억해두고 있다가 3초가 지나면 console.log(“두번째”)가 실행되는 것입니다.

그런데 이게 어떻게 가능한 것일까요?

그것을 이해하려면 일단 자바스크립트의 특징에 대해서 알고 있어야 합니다.


먼저, 자바스크립트는 “Single Thread” 기반의 언어라고 알려져 있습니다.

쓰레드에 대한 설명을 간단하게 하자면..

  1. Process

    • 운영체제로부터 자원을 할당받는 작업의 단위

    • Application( = Program)이 메모리에 로드된 상태

      => 실행중인 어플리케이션의 상태를 나타내는 도구

    • 프로세스들이 순번을 기다리며, 순번이 돌아오면 CPU의 자원(실행 권한)을 할당받음.

  2. Thread

    • 한 프로세스 내의 independent execution context
    • 프로세스가 할당받은 자원을 이용하는 실행의 단위(흐름)

결론적으로 Thread는 프로세스 내에 존재하는 무언가인데, Thread가 여러개라는건 무엇을 의미하는 걸까요?

바로, 할당받은 자원을 통해서 여러가지 행동을 할 수 있다는 것입니다.

3

반대로, V8과 같은 자바스크립트 엔진은 Single Thread기반의 프로그램이기 때문에 동시에 하나의 실행밖에 하지 못합니다.

하지만, 자바스크립트 엔진 이외에도 브라우저나 Node.js같은 환경들이 같이 코드 실행을 도와주기 때문에 Non-block function이 가능하게 됩니다.

밑의 그림은 브라우저를 도식화한 것인데요. ( 출처 )

4

우리가 알고 있는 setTimeout (Timer)나 XMLHttpRequest (Ajax)는 자바스크립트 엔진이 아닌 Web API 영역에서 처리됩니다.

심지어, 이런 Web API 영역은 주로 여러개의 쓰레드가 사용되기 때문에 사실상 자바스크립트가 ‘Single Thread’ 라는 말은 자바스크립트 엔진 내에서만 생각하면 좋을 것 같습니다.

2-3. Event Loop

그러면 어떻게 setTimeout이나 XMLHttpRequest 같은 함수들만 골라서 Web API가 담당할 수 있게 할까요?

바로 Event Loop와 Task Queue라는 장치가 자바스크립트 엔진과 Web API를 연동시켜주기 때문입니다.

일단 밑의 코드를 봅시다.

function sleep(sec) {
    const future = Date.now() + sec * 1000
    while (Date.now() < future) {}
}
function foo() {
    sleep(11); // 대략 11초가 걸림. 
    console.log('foo!'); // (3)
}
function baz() {
    console.log('baz!'); // (4)
}

setTimeout(baz, 10); // (1)
foo();

상식적으로 setTimeout이 10초를 기다리는거고, sleep이 11초를 기다리니까 setTimeout의 call back인 baz가 먼저 시행될 것이라 기대하지만,

5

아쉽게도 foo!가 먼저 시행되는 것을 볼 수 있습니다.

왜 그런지는 코드를 분석해보면서 Event Loop와 Task Queue에 대해서 알아봅시다.

  1. 0초 : setTimeout이 실행됨.

    • call stack에 추가되었다가 Timer에 이벤트를 요청하고 바로 제거됨.
      • call stack에서 제거되었다는 말은 다음 명령을 시행할 수 있다는 뜻입니다.
      • 명령을 시행한다는 말은 call stack에 명령을 추가한다는 뜻입니다.
  2. 0초 : foo()가 실행됨.

    • delay()
    • bar()
    • console.log(‘foo!’)

    들이 순차적으로 call stack에 기록되고 실행되고 있습니다.

  3. 10초 : Timer 이벤트가 종료되고, Web API는 setTimeout의 callback function인 baz를 Task Queue에 추가함..

  4. 11초 : delay()가 끝나고 콘솔에 ‘foo’가 찍히며 call stack이 비워진다.

  5. 11초 : Event Loop가 call stack이 비워진 것을 파악하고, Task Queue에 들어간 baz를 꺼내서 다시 시행합니다.

2-4. 요약

핵심은 다음 두가지입니다.

  • 모든 비동기 API들은 작업이 완료되면 콜백 함수를 태스크 큐에 추가한다. (일단은 비동기와 Non-block을 같다고 생각하는게 편합니다.)
  • 이벤트 루프는 ‘현재 실행중인 태스크가 없을 때’(주로 호출 스택이 비워졌을 때) 태스크 큐의 첫 번째 태스크를 꺼내와 실행한다.

3) 왜 사용할까?

결론적으로 10초 타이머가 상황에 따라서는 제대로 작동하지 않을 수 있다는 건데, 그럼에도 불구하고 왜 이런 방식을 사용하는 것일까요?

바로, 브라우저라는 특수한 환경 때문입니다.

function sleep(sec) {
    const future = Date.now() + sec * 1000
    while (Date.now() < future) {}
}
sleep(10);

이 함수를 동작시켜보면, 10초가 끝나기 이전에는 브라우저의 아무 장치도 사용할 수 없습니다.

우리가 윈도우에서 여러 프로그램을 다루는 상황이라면 동시에 여러 프로그램을 다룰 수 있기 때문에,

시간이 오래 걸리는 작업이 크게 문제가 되지 않지만, 브라우저 내에서는 치명적입니다.

따라서, 타이머나 I/O, API 같이 시간이 오래 걸리는 특수한 작업들은 Non-block을 통해 시행이 끝나지 않았음에도 불구하고 다음 작업을 막지 않는 것입니다.

3-1. Promise

그럼에도 불구하고 위에서 보았다시피 Non-block과 Async 함수들은 치명적인 단점이 있습니다.

  • 코드를 짠 순서대로 시행이 되지 않음.

  • 여러 call-back이 쌓이다보면 가독성도 떨어지고, 순서가 꼬이게됨.

    (Task Queue는 스택과는 달리 LIFO 구조이기 때문에 더더욱 그렇죠..)

과 같은 문제들이 있습니다.

그래서, 이것을 극복하기 위해서 Promise라는 개념이 생겼습니다.

일단 이 Promise와 Async가 무엇인지는 다음에 알아보도록 하겠습니다.

comments powered by Disqus