자바스크립트는 싱글 스레드 언어인데, 어떻게 동시에 여러 작업들을 수행하나요?

ChoiYongHyeun·2024년 12월 13일
2

브라우저

목록 보기
13/16
post-thumbnail

스레드란 무엇일까

우선 스레드란 무엇일지 먼저 알아보자

스레드란 어떤 프로그램 내에서 , 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다. [1]

콜스택

스레드는 프로세스를 동작시키기 위한 레지스터들을 가지고 있다.

싱글 스레드라는 것은 하나의 레지스터 그룹들만 존재한다는 것이다.

레지스터란 프로세스의 코드를 연산하기 위한 데이터를 저장하는 아주 작은 기억장치이다. [2]

CPU 는 레지스터에 저장된 데이터를 이용해 연산을 해나간다.

이를 통해 유추 할 수 있는 사실은 싱글 스레드의 경우엔 코드의 실행이 병렬적으로 일어 날 수 없으며 호출 순서에 맞춰 실행 될 수 밖에 없다는 것을 알 수 있다.

그것도 당연한 것이, 호출되는 코드에 따라 레지스터의 값을 변경해나가야 하기 때문이다.

예를 들어 a + b , c + d 라는 두 개의 코드가 존재 할 때 (a,b,c,d 는 변수) 에는 우선 레지스터에 첫 번째 호출인 a,b 변수를 레지스터에 담아 연산하고 , 그 다음엔 c,d 라는 변수를 레지스터에 담아 연산해야 하기 때문이다.

콜스택이란 이런 코드의 호출들의 흐름을 의미한다.

코드 a, b, c, d 가 순차적으로 호출 되었을 때 콜스택은 [a,b,c,d] 로 만들어지고

First In , Last Out 으로 [d, c , b ,a] 의 코드들이 호출 된다.

예시를 통해 알아보자

function multiply(x, y) {
    return x * y;
}

function square(num) {
    return multiply(num, num);
}

function main() {
    console.log(square(3));
}

main();

main 함수가 호출 되면 콜스택엔 main 이 담긴다.

[main]

이후 main 함수가 호출되면서 console.log(square(3)) 이란 값이 담긴다.

[main , console.log(sqaure(3))]

sqaure(3) 이 호출 되면서 multiply(3,3) 이 콜스택에 담긴다.

([main , console.log(square(3))] , multiply(3,3)])

이후 더 이상 담을 실행 컨텍스트가 없으면 하나씩 반환하면서 뿜뿜뺌 코드들을 실행하며 콜스택을 비운다.


자바스크립트는 싱글 스레드 언어이기 때문에 모든 코드들이 순차적으로 실행 된다는 것을 알았다.

이 말은 실행되는 하나의 코드라도 기간이 오래 걸리는 경우에는 콜스택에서 다음 코드가 실행되지 않기 때문에

브라우저 자체가 작동하지 않을 수 있다는 것을 의미한다.

서버로부터 데이터를 받아온 후 어떤 값을 브라우저에 렌더링 해야 하는 경우를 가정해보자

이 때 데이터를 받아오기 까지 기간이 3초가 걸린다고 가정했을 때

브라우저가 동기적인 처리만 지원한다면 3초간의 기간 동안 다른 코드들이 실행되지 않아 브라우저는 멈추게 될 것이다.

이러한 일을 방지하기 위해 자바스크립트에서는 프로미스 기반의 비동기 처리를 제공한다.

사실 프로미스에 대해 쓰기에는 이전에 썼던 포스트가 있어 해당 포스트와 관련 유튜브 영상을 참고 영상으로 올려둔다.
https://velog.io/@yonghyeun/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A3%A8%ED%94%84%EC%99%80-Promise-%EA%B0%9D%EC%B2%B4
https://www.youtube.com/watch?v=Xs1EMmBLpn4

비동기 처리

비동기 처리란 특정 코드가 실행이 완료 될 때 까지 기다리지 않고 다음 코드를 실행하는 방식을 의미한다. [3]

위에서 설명한 방식에 빗대어 다시 설명하면, 비동기 처리란 어떤 처리가 동작 할 때 스레드의 흐름을 지연하지 않고 다음 처리를 시행해나가는 방식을 의미한다.

위에서 이야기 했던 부분인 서버로부터 데이터를 받아오는 경우 데이터를 받아오는 동안 브라우저의 동작을 막는 것이 아니라

데이터를 받아오기 까지 정상적으로 스레드의 흐름을 유지하여 브라우저는 다른 작업을 진행하고 데이터를 받아 온 후 지정된 동작(데이터를 다루는) 을 시행하는 것을 의미한다.

자바스크립트는 이런 비동기 처리를 프로미스를 기반으로 한 이벤트 루프 , 태스크 큐를 이용해 비동기 처리를 한다.

WebAPI

자바스크립트는 싱글 스레드 언어이지만 브라우저라는 프로세스 자체는 멀티 스레드임을 기억하자

브라우저는 자바스크립트를 실행하는 스레드, 웹 api , 렌더링 엔진 , 네트워킹 스레드 , 서비스 워커 ... 등등 다양한 스레드가 함께 실행된다.

WebAPI 는 브라우저 자체가 제공하는 자바스크립트 런타임 외부에서 여러 태스크들을 처리하는 API로 타이머와 네트워크 요청 뿐 말고도 위에서 이야기 한 것 처럼 브라우저 렌더링, 돔 조작 , 돔 이벤트 , 인터렉션 등 다양한 기능을 처리 한다.

네트워크 요청인 Fetch 를 예시로 들면 WebAPI 의 일부로 자바스크립트 런타임에서 fetch가 호출되고 나면 네트워크 요청은 WebAPI , 네트워킹 스레드로 할당 되어 실행 된다.

네트워크 요청이 완료되어 자바스크립트에서 실행 되어야 할 때가 된다면 WebAPI 가 실행 결과를 자바스크립트 런타임인 이벤트 루프로 메시지를 전송 한다.

자바스크립트 런타임은 자바스크립트 코드가 실행되는 환경을 의미한다.

이렇게 브라우저의 다양한 스레드를 활용하여 자바스크립트 런타임 스레드를 블록 하지 않고 비동기 처리를 가능하게 된 것이다.

매우 많은 WebAPI 들이 존재한다. 깜짝 놀랐다. [5]

출처 : [4]

위 이미지가 자바스크립트 런타임을 간략하게 표현 한 것이다.

자바스크립트 런타임은 다른 스레드에서 전송하는 메시지를 받기 위한 공간인 큐가 존재 한다.

이 큐들은 다른 스레드에서 전송하는 메시지를 순차적으로 받은 후 받은 메시지를 콜스택에 올린 후 실행한다.

이 때 메시지는 객체가 될 수도, 콜백 함수가 될 수도 있다.

사실 자바스크립트에선 함수도 객체이기에 메시지는 객체 기반이라 봐도 될 것이다.

예를 들어 fetch 같은 경우는 Promise 객체가 WebAPI 에서 넘어오게 되고

setTimeout 같은 경우엔 내부에 존재하는 콜백 함수가 WebAPI 에서 넘어오게 된다.

마이크로 태스크 큐 , 태스크 큐

큐에는 태스크 큐와 마이크로 태스트 큐가 존재 한다.

이렇게 두 가지의 큐로 분류해둔 이유는 외부 다른 스레드에서 실행되어 런타임에서 콜스택에 올라가기를 대기하는 태스크들이 있을 때

우선순위에 맞춰 콜스택에 올리기 위함이다.

마이크로 태스크 큐는 우선순위가 더욱 높은 메시지들을 저장한다.

대표적으론 Promise 가 그렇다.

태스크 큐는 비교적 우선순위가 낮은 타이머나 , fetch 이후의 콜백 함수들, 돔에서 발생하는 이벤트 등이 존재한다.

이벤트 루프

이벤트 루프는 콜스택과 마이크로 태스크 큐 , 태스크 큐를 관리하는 루프로

콜스택이 비게 되는 경우 마이크로 태스크 큐가 빌 때 까지 마이크로 태스크 큐의 태스크들을 콜스택으로 이동 시키고

이후 태스크 큐에 있는 태스크들을 콜스택으로 이동시키는 역할을 한다.

profile
빨리 가는 유일한 방법은 제대로 가는 것이다

0개의 댓글