ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JS] JS는 싱글스레드 언어인데, 어떻게 비동기 처리가 가능할까? JS의 동작 원리.
    카테고리 없음 2024. 2. 1. 21:30
    728x90

    [JS] JS는 싱글스레드 언어인데, 어떻게 비동기 처리가 가능할까? JS의 동작 원리.

     

    알아보기에 앞서, 자바스크립트가 무엇인지 간략하게 알아보자.

    자바스크립트는 싱글 스레드 논 블로킹 비동기 언어이다. 여기서부터 단어의 의미를 몰라서 막막하다면 아래 게시물을 참고하자. 그런데 단어들의 의미를 알더라도 여전히 이해가 되지 않을 수 있다.

     

    (싱글 스레드가 무엇인지 모르겠나요?)

     

    [OS] Process와 Thread가 무엇일까?

    Process와 Thread process는 수행할 작업의 관리 단위이다. 이 관리의 주체는 OS이다. 연산이라는 연속적인 흐름에서 process 하나가 존재한다면, thread 또한 하나가 존재한다. process는 최소 1개의 thread를

    frorong.tistory.com

    (논 블로킹이 무엇인지 모르겠나요?)

     

    [[Block, Non-Block], [Sync, Async]]에 대해 알아보자!

    Block, Non-Block block과 non-block은 컴퓨터 프로그램에서 작업을 처리하는 두 가지 다른 방식을 말한다. 이름만 들어도 대충 어떤 느낌인지 감이 오는데.. block은 무언가가 막히고, non-block은 막히지 않

    frorong.tistory.com

     

    논 블로킹과 비동기가 함께 있는 것은 자연스럽다.

    아래 글은 위 블락, 논블락에 대한 내용 중 일부를 인용한 것이다.

    non-block이면서 sync인 상황, block이면서 async인 상황이 있을 수도 있다. 
    이게 무슨 말일까? non-block이면 당연히 async 아닌가?
    예를 들어 non-block 환경에서 caller함수가 addNum을 호출했을 때에 
    caller에 있던 제어권은 addNum으로 갔다가 다시 돌아온다. 이때 제어권이 돌아오면서 
    결괏값도 같이 반환될 수 있다.
    아직 계산이 끝나지 않았는데 결과값을 어떻게 반환하지?라고 생각할 수 있다. 그것이다. 
    아직 계산중이라는 상태를 결괏값으로 반환하는 것이다. 이렇게 되면 제어권과 결괏값이 
    동시에 반환되므로 동기적으로 동작하는 것이 된다. 따라서 non-block이면서 sync인 것이다.

     

    그리고 아래에 이어지는 내용이다.

    멀티 스레드 환경에서는 sync와 async, block과 non-block이 상호작용하여 다양한 작업이 이루어진다.
    어플리케이션에서는 process가 여러 개 작동할 수 도 있다. process 안에는 여러 개의 thread가 존재할 수 있는데 
    하나의 업무를 수행하더라도 그 업무를 세분화해서 각각 thread에 분담한다. 
    하나의 업무를 각각의 thread가 분할하여 수행하기 때문에 동기화라는 과정이 필요하다. 
    이 동기화는 1번 thread의 결괏값을 2번 thread가 기다리고 2번 thread의 결괏값을 3번 thread가 기다리는 
    방식으로 큐나 세마포어를 통해 관리된다.
    동기적인 작업에서는 하나의 thread가 작업을 수행하고 결과를 다음 thread에게 전달하며, 
    각 thread가 순차적으로 진행된다.
    비동기적인 작업에서는 각 thread가 독립적으로 작업을 수행하고, 결과를 기다리지 않고 다음 작업을 진행한다.
    sync/async, block/non-block와 multi thread는 상당히 긴밀한 관계가 있다.

     

    그런데 자바스크립트는 싱글 스레드인데, 비동기라고?? 앞뒤가 맞지 않다.

     

    이제부터 이 의문을 해결할 수 있도록 해보겠다.

     

    자바스크립트 런타임을 단순화하면 메모리 할당이 일어나는 힙과 스택을 볼 수 있다.

    자바스크립트는 스크립트 언어이다. 따라서 특정한 프로그램 안에서 동작해야 하는데, 일반적으로 자바스크립트는 브라우저 위에서 동작한다. 때문에 자바스크립트 런타임은 DOM, AJAX, timeout, event loop, callback queue등과 상호작용한다.

     

    먼저 자바스크립트 런타임의 call stack부터 알아보자.

     

    call stack

    아래의 코드가 있다. 이 코드를 실행한다면 당연하게도 1,2,3 순서로 콘솔이 찍힐 것이다.

    function example() {
      console.log(1);
      console.log(2);
      console.log(3);
    }
    
    example();

    좀 더 내부적으로 접근해보자. 자바스크립트는 싱글 스레드 언어이다. 함수가 실행되면 스택에 함수를 집어놓고 함수에서 리턴이 일어나면 가장 위의 함수를 꺼낸다.

     

    아래 그림과 같이 코드가 실행된다면, 가장 먼저 실행된 example()이 스택에 쌓인다. 이후 console.log(1)이 스택에 들어간 이후 리턴되어 꺼내진다. 2, 3도 마찬가지로 처리된 이후에 example()이 리턴되어 스택에서 꺼내진다.

    가끔 이런 오류를 본적이 있지 않은가? 맞다. 바로 이 콜스택의 사이즈를 범람하는 작업이 스택에 들어와 오류가 생긴 것이다.

     

    이를 실제로 테스트해보기 위해 아래의 코드를 크롬 개발자 도구에 입력해 보았다. 위 오류가 나타나는 것을 기대하고 말이다.

    function example() {
      let i = 0;
      while(i < 99999999){
        console.log(++i);
      }
    }
    
    example();

     

    과연 어떤 결과가 일어났을까?

    .

    .

    .

    결과는 실로 말할 수 없을 정도로 참혹했다.

    코드를 실행시키자 갑자기 클릭이 되지 않기 시작했고, 창을 종료하려 했지만 먹통이었다. 얼마 지나지 않아 열러있던 모든 크롬 탭이 잔인하게 종료되었다..

    터지기 직전 캡처한 화면이다. 무수히 많은 숫자가 찍히고 있다.

     

    이렇게 오류가 생기면서 코드가 멈추지 않고 브라우저가 강제로 종료된 이유는 브라우저는 일정 시간 동안 응답이 없는 스크립트를 감지하고 해당 스크립트를 종료하여 무한 루프나 고비용 연산을 방지하기 때문이다.

     

    때문에 내가 작성중이던 블로그 글은 무참히 파괴되어 사라졌다. 하지만 덕분에 글의 내용을 복기하며 다시 작성할 수 있었다.

    화면이 클릭되지 않은 이유는 무엇일까?

     

     

    blocking

    위에서 너무나 오래걸려 브라우저가 강제 종료된 코드처럼 느린 동작이 스택에 남아있는 것을 블로킹이라고 한다.

     

    프로그래밍 언어에서 싱글스레드는 여러 개의 스레드를 사용하지 않는다는 것을 의미한다. 여러 개의 스레드를 사용하지 않는다면 동기적으로 실행되는 코드가 콜 스택을 블로킹하면 브라우저는 다른 일들을 할 수 없다. 렌더링이나 이벤트 확인과 같은 다른 작업들을 할 수 없다. 때문에 위에서 클릭 이벤트도 감지할 수 없었던 것이다.

     

    브라우저는 단순한 런타임 이상을 가지고 있다. 자바스크립트 런타임은 한 번에 하나의 일만 할 수 있는데, 브라우저는 이를 넘어선 API를 지원하여 자바스크립트에서 호출할 수 있는 스레드를 지원한다.

    예를 들면 setTimeout과 같은 것이다. 자바스크립트가 실행되는 런타임 환경에 존재하는 별도의 브라우저 제공 API이다.

     

    setTimeout이 실행되면 내부적으로는 어떻게 동작할까?

     

    callback queue

    자바스크립트를 배운 사람이라면 위 코드의 실행 결과는 쉽게 예측할 수 있다. 당연히 2 이후에 1이 출력될 것이다.

    function example() {
      setTimeout(() => console.log(1), 1000);
      console.log(2);
    }
    
    example();

     

    내부적으로는 어떻게 동작할지 아래 그림을 통해 보자.

     

    setTimeout이 실행되면 먼저 call stack으로 이동한다. 그리고 web API에서 callback queue에 콜백을 넘긴다.

     

    callback queue에서 대기하던 console.log(1)이 1초 후에는 실행되어야 할 텐데 이는 어떻게 이루어질까?

    event loop

    그 것이 바로 event loop를 통해 이루어진다.

    event loop는 call stack과 task queue를 주시하는 파트를 담당한다. stack이 비어있고 queue에 callback이 존재한다면, event loop는 callback을 stack에 넣어준다. setTimeout의 callback인 console.log(1)도 event loop에 의해 call stack으로 이동한다.

    즉, callstack이 비어있어야 event loop를 통한 다음 동작이 가능한 것이다.

    이 과정이 비동기 함수가 호출되는 방식이다.

     

    브라우저는 자바스크립트의 동작에 크게 제약된다. 렌더도 하나의 callback처럼 작용하기 대문에 stack에 작업할 코드가 존재한다면 렌더링이 불가능하다. 앞서 나온 while문과 같이 느린 동기식 코드를 진행하면 렌더는 막히게 된다. 이와 같이 이미지 처리나 애니메이션 같은 느린 작업이 잦아지면 callback queue 관리에 지장이 생긴다.

     

    예시로 스크롤 이벤트가 생길 때마다 어떠한 동작을 수행한다면 callback queue에 엄청나게 많은 callback이 쌓이고 처리하며 성능에 문제가 생길 것이다. 이를 해결하기 위해 throttle이나 debounce같은 기법을 사용할 수 있다.

     

    throttle은 무수히 발생하는 이벤트들을 특정 주기마다 끊어서 처리하는 기법이고, debounce는 n초동안 이벤트가 추가로 발생하지 않는다면 한 번에 처리하는 기법이다.

     

    아래 PR은 최근 생성한 PR인데, throttle을 이용하여 스크롤 애니메이션의 이벤트를 처리하였다.

     

    Add projectIntroduce by frorong · Pull Request #350 · themoment-team/hello-gsm-front

    개요 💡 팀 소개내에 프로젝트 소개를 추가했습니다. ✨ 이 PR에서 핵심적으로 변경된 사항은 무엇인가요? 프로젝트 소개 카드를 추가하고, 애니메이션을 생성했습니다. 2024-01-31.4.00.30.mov 🔖 핵

    github.com

     

     

    마치며

    앞으로 서버 엔지니어께서

    "이건 프론트쪽에서 처리하는 게 좋지 않을까요?"
    라고 말하면 당당하게 이 글을 공유하며 이렇게 말하자.
    "유저들도 그걸 원할까요?"

    728x90
Designed by Frorong.