JavaScript는 왜 싱글 스레드인가 ?

작성자
JvaScript 는 왜 싱글 스레드(single threaded)로 설계를 했는지에 대해 알아보기전 제가 왜 주제를 이렇게 정했는지 간단히 적겠습니다.
주제를 Java Script는 왜 싱글 스레드인가에 대해 알아보게 된 이유는 큰 이유는 Back-End 개발자를 희망하게 되어 원래는 Java를 공부 하였는데 JavaScript가 Front-End와 Back-End 모두 사용할 수 있는 언어라고 학과 친구에게 들어 JavaScript를 공부하게 되었습니다. 근데 언어를 컴퓨터 공부할 때 함수, 명령어를 공부하는 것보단 내부에서 돌아가는 방식을 알고 코드를 작성하면 이해하기 더 쉽다 생각하여 JavaScript에 대한 정보를 찾다보니 JavaScript는 싱글 스레드를 사용한다는 정보를 얻었습니다.
하지만 정보를 계속 찾아보면 찾아볼 수록 싱글 스레드인데 왜 동시성을 가지고 비동기 처리를 하는지가 궁금해 더욱 파고들어 알아보고 싶다는 생각이 들어 다음과 같은 주제를 선정하였습니다.

 목차

JavaScript는 왜 싱글 스레드를 선택 했을까 ?

JavaScript는 웹 페이지의 보조적인 기능을 수행하려고 만든 프로그래밍 언어입니다.
기존 웹 개발자들이 사용하던 자바는 멀티 스레드를 사용하는데 다소 무겁고 어렵다는 인식과 더불어 동시성 문제도 있다보니 복잡하지 않은 싱글 스레드 형식을 채택한 것입니다.

싱글 스레드, 멀티 스레드, 스레드가 뭔데 ?

싱글 스레드와 멀티 스레드를 알기 전 스레드와 프로세스가 무엇인지 먼저 알고 넘어가겠습니다.

스레드 란 ?

한 줄로 설명하자면 “프로세스가 할당받은 자원을 이용하는 실행의 단위” 입니다.
그러면 프로세스는 뭐야 ? 라고 질문 할 수 있을 것 같습니다. 프로세스는 다음과 같습니다.
프로그램이 메모리에 올라가 실행된 상태 즉, 동적인 상태를 뜻하며 운영체제에서 할당하는 작업의 단위 입니다.
이 들의 특징은 다음과 같습니다.
프로세스는 스레드를 여러 개 생성해 여러 작업을 동시에 처리할 수 있습니다. 스레드들은 부모 프로세스의 자원을 공유하여 영향을 받아 데이터 공유가 가능합니다.
스레드는 프로세스 내에서 각각 Stack만 따로 할당받고 Code, Data, Heap 영역은 공유합니다.
같은 프로세스 안에 있는 여러 스레드들은 같은 힙 공간을 공유합니다. 반면에 프로세스는 다른 프로세스의 메모리에 직접 접근할 수 없습니다.
스레드는 싱글 스레드와 멀티 스레드로 나뉩니다. 싱글 스레드는 직렬을 형태로 하나의 스레드가 하나의 작업만 수행하여 순서대로 처리하는 모습을 볼 수 있다. 멀티 스레드는 병렬로 일을 처리하며 여러가지 스레드를 한꺼번에 처리합니다.
다음은 싱글 스레드와 멀티 스레드가 무엇인지 알아보겠습니다.

싱글 스레드 란 ?

싱글 스레드는 한 번에 하나의 작업만 처리할 수 있는 스레드입니다. 특징은 다음과 같습니다.
JavaScript의 실행 환경에서는 하나의 메인 스레드에서 모든 코드가 순차적으로 실행됩니다.
이는 코드 실행 순서를 보장하고 복잡도를 줄이며, 동시성 처리를 단순하게 합니다.
그러나 하나의 작업이 블로킹되면(예: 긴 시간이 걸리는 작업), 다음 작업이 지연될 수 있습니다.

멀티 스레드 란 ?

멀티 스레드는 여러 개의 스레드가 병렬로 실행되어 여러 작업을 동시에 처리할 수 있습니다. 특징은 다음과 같습니다.
병렬 처리는 여러 CPU 코어에서 동시에 실행되는 경우가 많습니다.
다중 작업을 동시에 처리할 수 있지만, 스레드 간 동기화와 데이터 공유 문제를 해결해야 합니다.
싱글 스레드와 멀티 스레드가 무엇인지 알아보았습니다. 근데 이런 의문이 들 수 있습니다.

싱글 스레드인데 어떻게 동시성을 가질 수 있는거야 ?

싱글 스레드는 분명 한 번에 하나의 작업만 처리할 수 있다고 하였습니다.
근데 JavaScript 로 만든 페이지는 “동시에 할 수 있는 것” 처럼 보입니다.
하지만 실제로 동작하는 원리는 다른 작업이 중간에 끼어들 수도 없고, 기존에 수행하던 작업이 끝나야만 그 다음 작업을 수행할 수 있습니다. JavaScript 를 실행하는 엔진은 총 두개로 Memory Heap 과 Call Stack 으로 이루어져있습니다.
JavaScript 는 하나의 메인 스레드에서 호출되는 함수들이 Call Stack 에 적재 될 것이고 Stack 의 처리 방식인 LIFO 방식으로 처리를 할 것입니다. Call Stack 과 싱글 스레드를 연관 지어 생각해보면 JavaScript 가 하나의 메인 스레드와 하나의 콜스택을 갖는다는 말인데 JavaScript 의 특징들을 찾아보면 비동기, 동시성, 논-블로킹 등의 상반되는 개념들이 검색됩니다.
동시성을 보장하는 비동기 작업들은 JavaScript 엔진을 구동하는 RunTime 환경에서 담당 하고 있기 때문에 RunTime 환경을 알아보겠습니다.
RunTime 환경은 웹 브라우저 혹은 Node.js를 의미하는데 글의 의미를 생각해 결론을 도출하려면 Node.js의 구조를 알아야 합니다.

Node.js의 구조

그림을 확인해보면 Node.js 안에 V8엔진, libuv 라이브러리가 있습니다. Node.js의 특성인 이벤트 기반, 논플로킹 I/O 모델들은 모두 libuv 라이브러리에서 구현됩니다.
Node.js의 핵심 라이브러리는 binding API 를 통해 JavaScript 환경에서 사용될 수 있는것이며 Node.js 에 동작하는 Event Loop는 libuv 라이브러리 안에서 구현됩니다. Event Loop는 libuv 라이브러리 안에서 실행되며 Node.js역시 JavaScript와 같은 싱글 스레드를 사용하기 때문에 하나의 Event Loop를 가지고 하나의 스레드가 모든 작업을 처리한다고 볼 수 있습니다.
결국 JavaScript가 싱글 스레드인 이유는 메인 스레드인 Event Loop가 싱글 스레드이기 때문인데 Event Loop가 뭔지 알아야 하니 Event Loop를 알아 봅시다.

Event Loop

Event Loop를 Google에 JavaScript EventLoop docs라 검색했을 때 나오는 MDN 문서에는 “큐의 다음 메세지를 처리합니다” 라 정의합니다.
JavaScript 실행 환경은 Node.js, 웹 브라우저라고 위에서 작성했습니다.
위 사진은 Event Loop가 동작하는 방식을 도식화 한 사진입니다.
JavaScript 코드에서 비동기로 처리해야하는 요소가 생기면 Web API 가 위임합니다.
Web API 에서 처리된 작업은 Callback Queue로 들어가 대기하고 있다 순서가 되면 Event Loop 가 Call Stack 을 확인해
비어있다면 Callback Queue 에 대기중인 요소들을 Call Stack 으로 넘겨줍니다.

 결론

동시성의 의미가 JavaScript 코드가 동시에 실행된다는 뜻은 아닙니다.
어차피 비동기 작업의 끝은 Event Loop 를 통해 결국엔 JavaScript 엔진의 Call Stack 으로 오게 되어있습니다.
참고자료