JavaScript는 왜 싱글스레드인가?

상태
완료
담당자
날짜
2024/06/26
숫자
0

JavaScript는 왜 싱글스레드인가?

웹 개발을 공부하다 보면 JavaScript라는 언어를 자주 접하게 됩니다. 하지만 정작 JavaScript에 대해 제대로 이해하고 있는 학생은 많지 않은데요. 오늘은 "JavaScript가 싱글 스레드인 이유"에 대해 이야기해보려고 합니다. 사실 저도 스레드와 동기, 비동기에 대해서 공부를 하다가 이 말을 처음 들었을 때는 왜 JavaScript가 싱글 스레드로 설계되었는지, 싱글 스레드인데 어떻게 비동기 작업을 처리할 수 있는지 잘 몰랐습니다. 그러나 웹 개발에 있어 중요한 개념이기 때문에, 저와 같은 초보자들이 이 내용을 왜 알아야 하는지 설명드리기 위해 이 글을 준비했습니다. 이 글을 이해하고 나면 JavaScript의 설계 이유, 동작원리와 구조, 비동기 작업을 처리하는 방식 등을 알 수 있을 겁니다. 그럼 먼저 스레드란 무엇인지부터 알아보겠습니다.

스레드란?

스레드(thread) :
프로세스 내에서 실행되는 가장 작은 단위.
프로세스(process)는 운영 체제에서 실행되는 프로그램 자체.
하나의 프로세스는 여러 개의 스레드를 가질 수 있으며, 이러한 스레드는 프로세스의 자원(메모리, 파일 등)을 공유함.
싱글 스레드 : 한 번에 하나의 작업만 처리할 수 있는 실행 방식.
싱글 스레드의 특징 :
단순함 : 코드 작성과 디버깅이 상대적으로 쉬움.
블로킹 : 하나의 작업이 완료될 때까지 다른 작업을 수행할 수 없음.
동기적 처리 : 작업이 순차적으로 실행됨.
멀티 스레드 :
멀티 스레드(multi-thread)는 여러 스레드를 사용하여 작업을 병렬로 처리하는 방식.
대부분의 서버 측 언어(예: Java, Python)는 멀티 스레드를 지원.
멀티 스레드의 특징 :
1.
병렬 처리: 여러 작업을 동시에 수행 가능 ⇒ 성능 향상, 작업 완료 시간 단축
2.
복잡성: 스레드 간의 자원 공유로 인해 동기화 문제(ex.데드락)가 발생할 수 있음. 따라서 코드 작성과 디버깅이 복잡해짐.
데드락 : 시스템에서 둘 이상의 프로세스가 서로의 작업이 완료되기를 무한정 기다리는 상태.
3.
비동기적 처리: 작업이 병렬로 실행되므로 동시에 여러 작업을 처리할 수 있음.
이를 통해 우리는 스레드가 무엇인지에 대해 알았고, 싱글스레드와 멀티스레드의 특징에 대해 알게 되었습니다. 그렇다면 이제 본격적으로 JavaScript는 어째서 싱글스레드인지에 대해 알아보겠습니다.

관점에 따른 분류

1.
언어적 관점 :
JavaScript가 싱글 스레드인 이유는 언어적 관점과 Node.js 관점으로 분류해볼 수 있습니다. 먼저 언어적 관점으로 JavaScript가 어째서 싱글 스레드인지에 대해 알아보겠습니다.
언어적 관점이란?
언어: 프로그래밍 언어는 컴퓨터에게 작업을 지시하기 위해 사용하는 공식적인 언어.
관점 : 무언가를 보는 시각이나 입장
언어적 관점 : '언어'를 중심으로 어떤 주제를 바라보는 관점. 웹 개발에서 언어적 관점은 주로 프로그래밍 언어를 중심으로 논의되며, 다양한 프로그래밍 언어가 가지고 있는 특징, 구조, 사용 목적 등을 고려하여 웹 애플리케이션 개발에 활용하는 방법을 의미.
자바스크립트가 언어적 관점에서 싱글 스레드인 이유:
JavaScript는 언어적 관점으로 보았을 때 싱글 스레드 언어입니다. JavaScript는 초기에 브라우저 환경에서 동작하도록 설계되어서, 단일 스레드에서 실행되기 때문입니다.
JavaScript가 싱글 스레드로 설계된 이유 :
JavaScript는 처음 브라우저에서 사용자 인터페이스를 조작하고 웹 페이지의 상호작용을 처리하기 위해 만들어졌습니다. 브라우저는 사용자 입력을 신속하게 처리해야 하므로 복잡한 멀티스레드 환경보다는 단순한 싱글 스레드 모델이 적합합니다. 단일 스레드는 코드가 순차적으로 실행되므로, 복잡한 동시성 문제를 피할 수 있기 때문이죠.
동기적 실행 :
JavaScript는 기본적으로 동기적 실행을 기본으로 하기에 설계 상 싱글 스레드입니다. (한 줄의 코드가 실행된 후 다음 줄이 실행됨.) 하지만 비동기적 작업(ex: 네트워크 요청, 파일 읽기/쓰기 등)을 처리하기 위해 콜백 함수, 프로미스, async/await와 같은 비동기 프로그래밍 패턴을 사용합니다. 이를 통해 싱글 스레드에서도 효율적으로 비동기 작업을 처리할 수 있습니다. 이러한 근거들을 바탕으로 우리는 언어적 관점으로 JavaScript가 싱글 스레드 기반의 언어라는 것을 알 수 있습니다.
2.
Node.js 관점 :
이번에는 Node.js의 관점으로 보았을 때 JavaScript가 어째서 싱글스레드인지 알아보도록 하겠습니다.
Node.js 관점이란?
Node.js : Node.js는 자바스크립트 런타임 환경. 이는 서버 측에서 자바스크립트를 실행할 수 있도록 해주는 플랫폼.
관점 : 무언가를 보는 시각이나 입장
Node.js 관점 : 웹 개발에서 Node.js를 사용하는 입장에서 바라보는 것을 의미. 이는 주로 서버 측 개발, 비동기 처리, 이벤트 기반 아키텍처 등을 포함.
서버 측 개발
Node.js는 서버 측에서 자바스크립트를 실행 가능하게 합니다.
이는 전통적인 웹 개발 방식에서 벗어나, 클라이언트 측과 서버 측 모두에서 자바스크립트를 사용할 수 있도록 하죠.
비동기 처리
Node.js의 주요 특징 중 하나는 비동기 처리를 기본으로 합니다.
이는 I/O 작업이 완료될 때까지 서버가 대기하지 않고 다른 작업을 계속 수행할 수 있게 합니다.
이벤트 기반 아키텍처
Node.js은 이벤트 기반 아키텍처를 채택하고 있습니다.
이는 이벤트 루프를 통해 비동기 작업을 처리하고, 콜백 함수를 통해 결과를 처리하게 합니다.
JavaScript가 Node.js 관점에서 싱글 스레드인 이유:
JavaScript는 Node.js 관점에서 싱글 스레드 언어입니다.
하나의 싱글 스레드와 하나의 콜스택을 갖고 있기 때문이죠.
하지만 비동기 작업을 처리할 때는 이벤트 루프(event loop)라는 메커니즘을 이용합니다.
⇒ 자바스크립트가 싱글 스레드로 동작하면서도 비동기 작업을 효율적으로 처리할 수 있게 해줌.
JavaScript의 작동 구조
위에서 이야기했듯이 JavaScript는 하나의 싱글스레드와 하나의 콜스택을 갖고 작업을 처리하지만 비동기 작업 시에는 이벤트루프라는 메커니즘을 이용합니다. 그렇다면 JavaScript에서 말하는 콜스택은 무엇이고, 이벤트루프를 이용하여 비동기 작업을 처리하는 과정은 어떻게 될까요? 이를 알아보기 전에 먼저 JavaScript 엔진의 구조를 알아보겠습니다.
Javascript의 런타임은 메모리 힙(memory heap)과 콜스택(call stack)으로 구성되어 있습니다.
1.
메모리 힙 : 메모리 할당을 담당하는 곳.
2.
호출스택(call stack) :
코드가 호출되면서, 스택이 쌓이는 곳. 함수가 호출되면 호출 스택에 추가되고, 함수 실행이 끝나면 호출 스택에서 제거됨.
하나의 메인스레드에서 호출된 함수가 하나의 콜스택에 쌓이기에 자바스크립트는 싱글스레드 기반 언어라고 볼 수 있습니다.
싱글스레드이면서 비동기 작업을 처리할 수 있는 이유
JavaScript는 싱글스레드 기반 언어이기에 런타임 자체적으로 비동기를 지원하지 않습니다.
동시성을 보장하는 비동기, 논블로킹 작업들은 Javascript 엔진을 구동하는 런타임 환경에서 담당게 됩니다. 여기서의 런타임 환경이란, 브라우저 혹은 Node.js를 뜻합니다. 런타임 환경은 JavaScript 엔진을 둘러싸고 있는 환경이며, 이벤트를 스케줄링 하는 역할을 합니다. 그렇다면 런타임 환경의 구조는 어떻게 될까요?
1.
웹 API : 브라우저에서 제공하는 API. 타이머 설정, 네트워크 요청 등의 작업을 처리. 이러한 작업은 호출 스택에서 실행되지 않고 웹 API로 전달됨.
2.
태스크 큐(Task Queue) = 콜백 큐 : 웹 API에서 완료된 비동기 작업의 콜백 함수가 대기하는 큐. 콜백 함수는 호출 스택이 비워지면 이벤트루프에 의해 호출 스택으로 전달됨.
3.
이벤트루프(Event Loop): 이벤트루프는 호출 스택과 태스크 큐를 모니터링하여 호출 스택이 비어 있는지 확인함. 호출 스택이 비어 있으면 태스크 큐에서 콜백 함수를 호출 스택으로 이동시켜 실행함.
비동기 코드 작동 예시
그렇다면 위에서 배운 것들을 바탕으로 JavaScript가 비동기 코드를 작동시키는 과정을 알아보겠습니다.
1. 먼저 코드는 호출스택에 쌓인 후 실행되면, Javascript의 엔진은 비동기 작업을 Web api에게 위임.
2. Web api는 해당 비동기 작업을 수행하고 콜백 함수를 이벤트 루프를 통해 태스크 큐에 넘겨줌.
3. 이벤트 루프는 콜스택에 쌓여있는 함수가 없을 때, 태스크 큐에서 대기하고 있던 콜백함수를 콜스택으로 넘겨줌.
4. 콜스택에 쌓인콜백함수가 실행되고, 콜스택에서 제거.
Node.js 의 구조
위의 그림에 나온 libuv에서 Node.js에 동작하는 이벤트 루프를 구현합니다. 하지만 이벤트 루프가 libuv 내에서 실행된다고 해서, Javascript의 스레드와 이벤트 루프의 스레드가 별도로 존재하는 것은 아닙니다. Node.js는 싱글스레드이기 때문에 하나의 이벤트 루프를 가지며, 하나의 스레드가 모든 것을 처리합니다.

결론

이렇게 우리는 JavaScript가 싱글스레드인 이유를 관점에 따라 분류하여 알아보았고 더 나아가 JavaScript의 작동 구조까지 알아보았습니다. 정리하자면 JavaScript가 싱글 스레드인 이유 중 언어적 관점으로는 JavaScript가 초기에 브라우저 환경에서 동작하도록 설계되어, 단일 스레드에서 실행되기 때문이고, Node.js 관점으로는 하나의 메인스레드와 하나의 콜 스택을 가지며, 비동기 작업은 이벤트 루프라는 메커니즘을 통해 비동기 작업을 처리하기 때문이라는 것을 알았습니다.
이번에 배운 내용을 통해 저는 다른 학생들이 싱글 스레드 모델의 특징과 구조를 이해하여 웹 개발을 하는데에 있어 도움이 되었으면 합니다.