본문 바로가기
Modern React Deep Dive/01장 리액트 개발을 위해 꼭 알아야 할 자바스크립트

1.5.1 싱글 스레드 자바스크립트

by Whiimsy 2024. 5. 14.

1.5.1 싱글 스레드 자바스크립트

자바스크립트는 싱글 스레드에서 작동. 즉, 기본적으로 자바스크립트는 한 번에 하나의 작업만 동기 방식으로만 처리할 수 있음. 동기(synchronous)는 직렬 방식으로 작업을 처리하는 것을 의미, 이 요청이 시작된 이후에는 무조건 응답을 받은 이후에야 다른 작업 처리 가능. 그동안 다른 모든 작업은 대기. 때문에 한 번에 다양한 많은 작업 처리 불가능. 반대 의미의 비동기(asynchronous)는 병렬 방식으로 작업을 처리하는 것. 요청을 시작한 후 이 응답이 오건 말건 상관없이 다음 작업이 이루어지며, 한 번에 여러 작업 실행 가능.

싱글 스레드 기반의 자바스크립트에서도 많은 양의 비동기 작업이 이루어짐. 모던 웹 애플리케이션에서는 사용자에게 많은 양의 정보를 다양한 방식으로 제공하기 위해 많은 것이 비동기로 작동함. 리액트 또한 과거 렌더링 스택을 비우는 방식으로 구현됐던 동기식의 렌더링이 16 버전에 접어들면서 비동기식으로 작동하는 방법이 소개됨. 이처럼 리액트에도 비동기식으로 작동하는 작업이 존재.

프로세스와 스레드

과거에는 프로그램을 실행하는 단위가 프로세스뿐이었음. 프로세스(process)란 프로그램을 구동해 프로그램의 상태가 메모리상에서 실행되는 작업 단위를 의미. 즉, 하나의 프로그램 실행은 하나의 프로세스를 가지고 그 프로세스 내부에서 모든 작업이 처리되는 것을 의미. 그러나 하나의 프로그램에서 동시에 여러 개의 복잡한 작업을 수행할 필요성이 대두됐고, 그래서 탄생한 것이 더 작은 실행 단위인 스레드(thread). 하나의 프로세스에서는 여러 개의 스레드를 만들 수 있고, 스레드끼리는 메모리를 공유할 수 있어 여러 가지 작업을 동시에 수행할 수 있음. 이에 따라 프로세스 내부에서 여러 개의 스레드를 활용하면서 동시 다발적인 작업 처리 가능.

  • 프로세스: 프로그램을 구동해 프로그램의 상태가 메모리상에서 실행되는 작업
  • 스레드: 프로세스보다 더 작은 실행 단위로, 하나의 프로세스에서 여러 개의 스레드를 만들 수 있음

 

자바스크립트는 왜 싱글 스레드로 설계됐을까? 멀티 스레드는 내부적으로 처리가 복잡함. 스레드는 하나의 프로세스에서 동시에 서로 같은 자원에 접근할 수 있는데, 동시에 여러 작업을 수행하다 보면 같은 자원에 대해 여러 번 수정하는 등 동시성 문제가 발생할 수 있어 이에 대한 처리가 필요. 또한 각각 격리돼 있는 프로세스와는 다르게, 하나의 스레드가 문제가 생기면 같은 자원을 공유하는 다른 스레드에도 동시에 문제가 일어날 수 있음. 이러한 특징을 토대로 과거 자바스크립트의 역할을 다시금 상기해 볼 필요가 있음. 최초의 자바스크립트는 브라우저에서 HTML을 그리는 데 한정적인 도움을 주는 보조적인 역할로 만들어졌었음.

 

자바스크립트는 1995년경 첫선을 보임. 이 당시에는 아직 멀티 스레드에 대한 개념이 대중화되던 시기가 아녔음. 그리고 1995년에는 브라우저에서 아주 간단한 스크립트를 지원할 목적으로 LiveScript(자바스크립트의 전신)를 만들었던 것이 자바스크립트의 시작. 최초의 자바스크립트는 이름에서 유추할 수 있는 것처럼 다른 여러 가지 언어를 참고해 단 10일 만에 첫 버전이 완성됨. 최초의 자바스크립트는 단순히 버튼 위에 이미지를 띄우거나, 간단한 경고창을 띄우거나, 폼을 처리하는 등 아주 기초적인 수준에서만 제한적으로 사용됨. 즉, 설계 당시에는 자바스크립트로 웹페이지에서 벌어지는 다양한 일을 처리해야 할 것이라고 생각하지 못했을 것. 또, 만약 자바스크립트가 멀티 스레딩을 지원해서 동시에 여러 스레드가 DOM을 조작할 수 있다면 어떻게 될까? 멀티 스레딩은 메모리 공유로 인해 동시에 같은 자원에 접근하면 타이밍 이슈가 발생할 수 있고, 이는 브라우저의 DOM 표시에 큰 문제를 야기할 수 있음.

 

이러한 상황을 빗대어 보았을 때, 자바스크립트를 싱글 스레드 언어로 구현한 것은 나름 합리적인 결정. 자바스크립트가 다양한 역할을 하게 되면서 개발자에 따라서는 자바스크립트 설계 자체가 바뀔 때가 됐다고 보기도 함.

 

자바스크립트가 싱글 스레드라는 것은 무엇을 의미할까? 자바스크립트 코드의 실행이 하나의 스레드에서 순차적으로 이루어진다는 것 의미. 이는 코드를 한 줄 한 줄 실행한다는 것의 의미하며 궁극적으로 하나의 작업이 끝나기 전까지는 뒤이은 작업이 실행되지 않는다는 것 의미. C 언어나 다른 메이저 프로그래밍 언어에서는 스레드에서 실행 중인 함수를 시스템이 임의로 멈추고 다른 스레드의 코드를 먼저 실행할 수 있지만 자바스크립트에는 그런 기능이 존재하지 않음. (Node.js에서 새롭게 추가된 Worker나 브라우저에서 제공하는 WebWorker를 활용하면 동시에 여러 작업을 처리할 수 있지만 이 두 기능은 매우 최근에 나온 것으로 번 절에서는 논외로 함.)

 

자바스크립트에서 하나의 코드가 실행하는 데 오래 걸리면 뒤이은 코드가 실행되지 않음. 이러한 자바스크립트의 특징을 "Run-to-completion"라고 함. 이러한 특징은 자바스크립트 개발자에게 동시성을 고민할 필요가 없다는 장점이 되지만, 반대로 때에 따라서 웹페이지에서는 단점이 될 수 있음. 하나의 작업이 끝나기 전까지는 다른 작업이 실행되지 않으므로 어떠한 작업이 오래 걸린다면 사용자에게 마치 웹페이지가 멈춘 것 같은 느낌을 줄 수 있음. 결론적으로 Run-to-completion, 즉 자바스크립트의 모든 코는 '동기식'으로 한 번에 하나씩 순차적으로 처리됨.

 

그렇다면 비동기란? 자바스크립트에서 비동기 함수를 선언할 때 쓰는 async는 영어로 'asynchronous', 즉 동시에 일어나지 않는 것 의미. 동기식과 다르게 요청한 즉시 결과가 주어지지 않을 수도 있고, 따라서 응답이 언제 올지도 알 수 없음. 그러나 동기식과 다르게 여러 작업을 동시에 수행할 수 있다는 장점이 있음.

 

console.log(1)

setTimeout(() => {
  console.log(2)
}, 0)

setTimeout(() => {
  console.log(3)
}, 100)

console.log(4)

 

setTimeout(callback, 0)은 실제로 즉시 실행되지 않고, 현재 실행 중인 코드 블록이 완료된 다음에 실행됨. 즉, 동기적인 작업과 현재 실행 중인 태스크가 완료되면 이벤트 큐에 쌓여있는 작업 중에서 setTimeout(callback, 0)으로 등록된 콜백 함수가 실행되는 것. 따라서 콘솔 출력의 순서가 1, 4, 2, 3 순설로 나타남. 그런데 자바스크립트는 싱글 스레드로 작동하기 때문에 "Run-to-completion"으로 작동해야 하므로 1, 2, (0.1초 후에) 3, 4로 출력되어야 정상 아닌가? (아님) 동기식으로 작동하는 자바슨크립트 세상에서 어떻게 이런 비동기 코드를 처리할 수 있는 것일까. 이러한 사실을 이해하려면 '이벤트 루프'라는 개념을 이해해야 함.