자바스크립트는 왜 싱글 쓰레드일까?
자바스크립트라는 언어는 싱글 쓰레드 언어라고 알려져있다. 보통 싱글 쓰레드라고 하면 한 번에 하나의 작업만 수행할 수 있다고 생각한다. 그렇다면 자바스크립트를 주로 사용하는 웹 사이트에서는 어떻게 한번에 여러 요청을 받을까? 그리고 여러 요청이 오갈 수 있는 자바스크립트는 왜 싱글 쓰레드일까? 오늘은 이 주제에 대해 알아보려고 한다.
자바스크립트는 정말 싱글 쓰레드일까?
맞다, 정확하게 말하면 자바스크립트의 메인 쓰레드인 이벤트 루프가 싱글 쓰레드이기 때문에 자바스크립트를 싱글 쓰레드 언어라고 부른다. 하지만 이벤트 루프만 독립적으로 실행되지 않고 웹 브라우저나 NodeJS같은 멀티 쓰레드 환경에서 실행된다. 즉, 자바스크립트 자체는 싱글 쓰레드가 맞지만 자바스크립트 런타임은 싱글 쓰레드가 아니다.
싱글 쓰레드로 어떻게 한번에 여러 요청을 처리할까?
기존 동기식 요청은 코드를 한줄 한줄 차례대로 실행한다. 그래서 하나의 작업에 걸리는 시간에 관계 없이 첫 번째 코드가 실행 된 뒤 다음 코드가 실행된다. 이렇게 되면 앞의 작업시간이 길수록 시간 및 자원의 낭비가 심해진다. 하나의 요청이 완료될 때 까지 기다리지 않고 동시에 다른 작업을 실행하는 비동기 호출로 극복할 수 있다.
그렇다면 자바스크립트는 싱글 쓰레드로 동작하며 어떻게 한번에 여러 요청을 처리할까? 바로 비동기 작업을 통해 여러 요청들을 처리하게 된다. 그렇다면 비동기 작업은 어떻게 동작할까?
자바스크립트 비동기 런타임 과정
자바스크립트가 비동기 코드를 어떻게 동작시키는지 알아보기 위해선 일단 자바스크립트 런타임 환경에 대해 알아야 한다. 여기서는 간단하게 비동기를 동작하는데 필요한 요소들만 살펴보자!
자바스크립트가 실행될 때는 다음과 같은 요소들이 실행을 도와준다.
- 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'
이 위에 위치함에도 비동기 코드라 나중에 출력되는 것을 볼 수 있다. 어떻게 이렇게 동작하는 것일까?
처음에 우선 setTimeout
함수가 실행되며 Call Stack에 setTimeout
함수가 추가된다.
setTimeout
함수는 자바스크립트 엔진이 처리하지 않고 Web API가 처리한다. (NodeJS의 경우 Timers 모듈) setTimeout
함수는 Web API의 Timeout작업을 요청한 시간이 지나면 Task Queue로 인자로 받은 callback함수를 전달한다.
그리고는 두 번째 라인에 작성한 console.log
가 Call Stack에 추가된다. 그리고 Call Stack의 console.log
가 실행되며 콘솔에는 'hello chanyeong'
이라는 문자열이 출력된다.
이 때, 자바스크립트의 Event Loop는 Call Stack이 비어있는지 항상 확인하는데 방금 console.log
가 실행되며 Call Stack이 비워진 것을 확인한다.
Call Stack이 비워진 것을 확인한 Event Loop는 Task Queue에 있던 callback함수를 Call Stack으로 옮겨 작업을 수행한다. 콘솔에는 'async chanyeong'
이 추가로 출력된 것을 볼 수 있다.
모든 작업이 끝나게 되면 다음과 같이 Call Stack과 Task Queue가 비워진 것을 볼 수 있다. 자바스크립트의 비동기 작업은 다음과 같이 실행된다.
자바스크립트는 왜 싱글 쓰레드일까?
그렇다면 오늘 이 포스트를 작성한 이유인 자바스크립트가 왜 싱글 쓰레드로 구성되어 있는지 알아보자.
지금까지 자바스크립트가 싱글 쓰레드로 여러 요청을 처리하는 과정을 살펴봤는데 아직 이해가 가지 않는 부분이 조금 있다. 저 과정 자체는 모두 이해가 가는데 아직 내 머릿속엔 왜 싱글 쓰레드야?🤔 라는 물음이 아직 그치지 않았다. 내 단순한 생각으로는 "그냥 멀티 쓰레드를 써도 되지않아?"라는 생각이 들었기 때문이다.
이유는 바로 쉬워서이다. 만약 자바스크립트가 멀티 쓰레드로 실행되는 언어였다면 웹페이지에서 발생하는 동시성 문제에 대해 해결해야 했다. 실제로 멀티 쓰레드로 구현된 서비스에서는 이 동시성 문제에 대해 정말 많은 신경을 쓴다. 하지만 자바스크립트는 단일 쓰레드로 실행되므로 인해 교착 상태와 같은 다중 쓰레드 환경에서 발생할 수 있는 복잡한 시나리오를 신경 쓸 필요가 없으며 비동기 처리를 통해 쉽게 여러 요청을 처리할 수 있다.
실제로 구글의 Chrome 브라우저 마저도 기존 웹 페이지에서 엄청난 동시성 문제를 일으킬 수 있다는 이유로 단일 웹 사이트 페이지의 자바스크립트 코드가 동시에 실행되는 것을 허용하지 않는다.
결론
지금까지 별 생각없이 자바스크립트의 비동기 코드를 작성하곤 했다. 물론 작성하면서 동시성에 대한 문제를 거의 고려하지 않기도 했다. 이것 자체가 자바스크립트가 싱글 쓰레드로 실행되는 이유인 것 같다. 개발자가 동시성에 대해 발생할 수 있는 복잡한 시나리오를 고려하지 않고도 쉽게 서비스를 만들 수 있었다. 물론 저런 동작 개념을 파악하고 있다면 더 좋은 서비스를 개발할 수 있다고 생각한다. 그러니 앞으로 틈틈히 당연하다고 생각한 개념들에 왜?🤔라는 물음을 던지는 개발자가 되고싶다.
참고
본 포스트는 다음 문서를 참고해 작성했습니다.
https://www.geeksforgeeks.org/why-doesnt-javascript-support-multithreading/?ref=rp
https://www.geeksforgeeks.org/why-javascript-is-a-single-thread-language-that-can-be-non-blocking/