ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 항해3주차 - Node.js (38) 동기와 비동기
    항해 2023. 1. 26. 19:04

    1. 자바스크립트 엔진은 싱글 스레드로 동작 한다.

     

    프로그램이란 어떤 작업을 위해 실행할 수 있는 파일을 의미하며, 해당 파일이 실행되면,

    하나의 프로세스가 실행되게 되어 운영체제로부터 시스템 자원(메모리와 연산능력)을 할당받게 된다.

    이러한 자원을 통해 프로그램이 컴퓨터에서 연속적으로 프로세스를 실행 할 수 있게 되는 것이며,

    프로세스란 보통 '실행된' 프로그램을 이야기한다.

    여기서 하나의 프로세스내에서 실행되는 흐름의 단위로 스레드가 있다.

    프로세스가 할당 받은 자원의 일부를 할당받거나 공유하며 실제로 우리 프로그램이 수행을 한다고 생각하시면 좋다.

    일반적으로 쓰레드는 "일꾼"으로 비유가 된다.

     

    여기서 프로세스가 수행할 코드는 프로그래밍 언어로 작성되어 기계어로 번역되어

    순차적으로 수행되는 것이고, 이 과정이 컴파일이다.

    여기서 자바스크립트의 특징적인 특징이 하나 더 추가되는데, 일반적으로 프로그래밍 언어로 작성된 프로그램이 프로세스가 되기 위해서는 "운영체제(mac os, windows)"로부터 시스템의 자원을 할당받아야 하는데, 사실 자바스크립트에서는 프로세스와 운영체제 사이에 "브라우저(safari, chrome, window edge)"가 있다.

     

    결론적으로 자바스크립트가 싱글 스레드로 동작한다는 것은,

    하나의 일꾼을 가지고 우리가 작성한 코드를 읽고 수행해 나간다고 생각해주시면 될 것 같다.

     

    • 싱글 스레드 장점

    (1) 프로그래밍의 난이도가 상대적으로 쉽다

    (2) 멀티 스레드로 작업하는 것보다 비용이 적다

    (3) 멀티 스레드로 작업하는 것보다 빠르다

     

    • 싱글 스레드 단점

    (1) 연산량이 많거나 구조적으로 시간이 걸리는 작업할 경우 그 작업이 완료되어야 다른 작업을 수행한다

    (2) 하여 에러리에 신경을 더 써야 한다.

     

    그렇다면 연산량이 많거나 구조적으로 시간이 걸리는 작업을 하는 경우란 어떤 경우가 있을까?

    특히 연산량이 많은 작업이라는 것은 이해가 가는데, 구조적으로 시간이 걸리는 작업이라는것은 어떤 것이 있을까?
    그리고 그러한 작업이 끝나야 다른 작업을 수행해야 한다는 것이 왜 단점으로 작용한다는 것일까?
    첫 번째로 연산량이 많거나 구조적으로 시간이 걸리는 작업 들의 예시는, 보통 "렌더", "통신" 등에서 많이 발생한다.
    전자는 말 그대로 연산량이 많아서 시간이 소요되고, 통신같은 경우는 스레드가 다음 줄을 읽어나갈 준비가 되는 동안 통신과 관련한 코드의 응답이 오지 않았다면 기다려야 하는 상황이 온다.
     
    // Some code
    
    let 헤드라인들;
    
    function getDataFromServer() {
        함수야 저기 www.naver.com에 가서, 
        거기에 저장되어있는 뉴스 헤드라인좀 쭉 받아와줘,    
        return [네이버 서버에서 받아온 헤드라인들];
    }
    
    function renderNewsHeadLine(headLineArr) {
        함수야 헤드라인들이 들어있는 배열이 들어오면,
        html <div/> element를 이렇게 저렇게 예쁘게 만들어서,
        갯수만큼 화면에 뿌려줘!
        return;
    }
    
    헤드라인 = getDataFromServer();
    renderNewsHeadLine(헤드라인);

    위와 같은 상황은 매우 자주 일어나는 상황이다. 실제로 위의 프로그램을 실행시키면 어떻게 될까?

    정답은 아무것도 렌더되지 않는다.

    getDataFromServer()라는 함수가 실행되고 해당 요청을 보내고 응답을 기다리는 동안,

    우리의 cpu는 다음 줄을 읽어내려갈 준비가 되고도 남았고, 실제로 그냥 읽어내려갔기 때문이다.

    결국 renderNewsHeadLine() 함수는 빈 배열을 건내받았기 때문에, 아무것도 렌더되지 않고 끝나게 된다.

     

    더보기

    매니저님이 비동기와 동기에 관해 예시로 들어준 이야기!

    만약 카페에 갔는데 앞에 복잡한 주문이 100개가 밀려있어. 그런데 나는 아메리카노 1잔만 주문할거야.

    그럼 융통성있게 간단한 아메리카노 1잔 먼저 처리하고 나머지 100잔을 처리해도 되겠지?

    이게 비동기임.

    비동기 처리의 대표적인 그림 예시

    2번째 그림의 상황처럼 시간이 오래 걸리지 않는 주문을 받는 일은 몰아서 처리를 해둔다.

    그리고 커피의 제조같이 시간이 소요되는 일은 뒤에서 담당을 해준다.

    프로그램은 주문을 받아주고 주문이 완료되면 다시 해당 고객을 불러 커피를 건낸다.

    그럼 고객이 무의미하게 기다리는 시간은 점점 사라질테고 점원의 처리 속도만큼 빨라지겠네!

    그리고!!

    뒷방에서 커피를 제조하는 놈도, 요청을 보내놓고 기다리는 놈도 모두 스레드임!!!

    그런데 여전히 자바스크립트는 싱글스레드로 동작하는 것도 맞다.

    어떻게 가능??how?

    because 브라우저!

    브라우저는 다른 서버나 네트워크와 통신기능도 탑재되어있고, html & css를 그려주는 기능도 있고,

    내부적으로 자바스크립트 언어로 작성된 프로그램을 돌릴 수 있는 운영체제 비슷한것도 있다.

    결론적으로 자바스크립트 엔진은 싱글 스레드로 작동하지만 비동기 처리를 하기 위해 있는 별도의 스레드들이 있다.

     

     

    2. 왜 자바스크립트는 싱글 쓰레드 일까?

    정확하게 말하면 자바스크립트의 메인 쓰레드인 이벤트 루프가 싱글 쓰레드이기 때문에

    자바스크립트를 싱글 쓰레드 언어라고 부른다.

    하지만 이벤트 루프만 독립적으로 실행되지 않고 웹 브라우저나 NodeJS같은 멀티 쓰레드 환경에서 실행된다.

    즉, 자바스크립트 자체는 싱글 쓰레드가 맞지만 자바스크립트 런타임은 싱글 쓰레드가 아니다.

     

    3. 싱글 쓰레드로 어떻게 한번에 여러 요청을 처리할까?

    기존 동기식 요청은 코드를 한줄 한줄 차례대로 실행한다.

    그래서 하나의 작업에 걸리는 시간에 관계 없이 첫 번째 코드가 실행 된 뒤 다음 코드가 실행된다.

    이렇게 되면 앞의 작업시간이 길수록 시간 및 자원의 낭비가 심해진다.

    하나의 요청이 완료될 때 까지 기다리지 않고 동시에 다른 작업을 실행하는 비동기 호출로 극복할 수 있다.

    자바스크립트는 싱글 쓰레드로 동작하며 어떻게 한번에 여러 요청을 처리하냐면, 바로 비동기 작업을 통해서 처리가능!

     

    4. 자바스크립트 비동기 런타임 과정

                                                     실행 컨텍스트↓

    자바스크립트가 실행될 때는 다음과 같은 요소들이 실행을 도와준다.

    • Call Stack: 자바스크립트에서 수행해야 할 함수들을 순차적으로 스택에 담아 처리
    • Web API: 웹 브라우저에서 제공하는 API로 AJAX나 Timeout등의 비동기 작업을 실행
    • Task Queue: Callback Queue라고도 하며 Web API에서 넘겨받은 Callback함수를 저장
    • Event Loop: Call Stack이 비어있다면 Task Queue의 작업을 Call Stack으로 옮김

    비동기 코드가 동작하는 과정 예시

    setTimeout(() => console.log('async chanyeong'));
    console.log('hello chanyeong');
    
    // hello chanyeong
    // async chanyeong

    다음 코드는 'hello chanyeong' 'async chanyeong'이라는 문자열을 콘솔에 출력하는 간단한 코드이다.

    하지만 'async chanyeong'이 위에 위치함에도 비동기 코드라 나중에 출력되는 것을 볼 수 있다.

    어떻게 동작하는거?

     

    (1) 처음에 우선 setTimeout 함수가 실행되며 Call Stack에 setTimeout 함수가 추가된다.

     

    (2) setTimeout 함수는 자바스크립트 엔진이 처리하지 않고 Web API가 처리한다.

         NodeJS의 경우 Timers 모듈) setTimeout함수는 Web API의 Timeout작업을 요청한 시간이 지나면 T

         ask Queue로 인자로 받은 callback함수를 전달한다.

    (3) 두 번째 라인에 작성한 console.log가 Call Stack에 추가된다.

         그리고 Call Stack의 console.log가 실행되며 콘솔에는 'hello chanyeong'이라는 문자열이 출력된다.

    (4) 이 때, 자바스크립트의 Event Loop는 Call Stack이 비어있는지 항상 확인하는데

          방금 console.log가 실행되며 Call Stack이 비워진 것을 확인한다.

    (5) Call Stack이 비워진 것을 확인한 Event Loop는 Task Queue에 있던 callback함수를

         Call Stack으로 옮겨 작업을 수행한다. 콘솔에는 'async chanyeong'이 추가로 출력된 것을 볼 수 있다.

    (6) 모든 작업이 끝나게 되면 다음과 같이 Call Stack과 Task Queue가 비워진 것을 볼 수 있다.

          자바스크립트의 비동기 작업은 다음과 같이 실행된다. 

     

     

    5. 자바스크립트는 왜 싱글쓰레드 일까?

    그냥 멀티 쓰레드를 쓰는게 낫지 않나? 라고 생각할 수 있지만 싱글 쓰레드를 쓰는 이유는

    "쉬워서!"

    만약 자바스크립트가 멀티 쓰레드로 실행되는 언어였다면 웹페이지에서 발생하는 동시성 문제에 대해 해결해야 했다.

    실제로 멀티 쓰레드로 구현된 서비스에서는 이 동시성 문제에 대해 정말 많은 신경을 쓴다.

    하지만 자바스크립트는 단일 쓰레드로 실행되므로 인해 교착 상태와 같은 다중 쓰레드 환경에서 발생할 수 있는

    복잡한 시나리오를 신경 쓸 필요가 없으며 비동기 처리를 통해 쉽게 여러 요청을 처리할 수 있다.

    실제로 구글의 Chrome 브라우저 마저도 기존 웹 페이지에서 엄청난 동시성 문제를 일으킬 수 있다는 이유로

    단일 웹 사이트 페이지의 자바스크립트 코드가 동시에 실행되는 것을 허용하지 않는다.

     

    결론은 비동기 코드로 작성하면 동시성에 대한 문제를 고려하지 않아도 되니까 

    자바스크립트는 싱글 쓰레드로 실행되는 것 같다.

    댓글

Designed by Tistory.