javascript는 싱글스레드 , 논블라킹 언어입니다.
무슨 말인지 모르시겠지만 하나씩 해결해보도록 하겠습니다!
쓰레드란 프로세서 내부에서 일을 하는 주체를 말합니다.
아주 이해하기 쉬웠던! 에브리타임에서 유명한 글을 가져와 보았습니다.
참고하시면 좋을듯 합니다!
그렇다면 싱글스레드는 무엇일까?
이름에서 쉽게 유추할 수 있듯 하나의 스레드를 가지고 있다는 것을 의미한다.
여기에 더하여 우리는 하나의 개념을 더 챙겨 보자
싱글 스레드 === 하나의 콜스택 === 한번에 하나의 동작
그렇다면 멀티 스레드는 어떨까?
멀티 스레드 === 여러 콜스택 === 한번에 여러 동작
어떤가 비교적 쉽게 이해할 수 있음을 알 수 있다!
문맥 교환(context switch) 작업을 요구하지 않는다.
문맥교환이란 하나의 프로세스가 CPU를 사용 중인 상태에서 다른 프로세스가 CPU를 사용하도록 하기 위해, 이전의 프로세스의 상태(문맥)를 보관하고 새로운 프로세스의 상태를 적재하는 작업을 말한다
자원 접근에 대한 동기화를 신경쓰지 않아도 된다.
스레드들이 동시에 동일한 자원에 접근하는 것을 막아주어야 하는데 이에 대한 노력을 하지 않아도 괜찮습니다.
프로그래밍 난이도가 쉽고, 자원을 적게 소모한다.
CPU만을 사용한 연산을 수행할 시, 오히려 멀티 스레드보다 속도가 빠르다
멀티 스레드의 경우 context switching 과정에서 시간을 소모 할 수 있기 때문, 또한 멀티 스레드는 짧은 시간동안 여러 스레드를 돌아가면서 사용하기 때문에 동시에 사용되는 것처럼 보이는 동작을 수행한다.
연산량이 많은 작업을 수행하는 경우 해당 작업이 마무리 될때까지, 아무런 동작을 하지 못한다. (블로킹)
싱글 스레드는 에러가 발생하고 에러를 처리하지 못하는 경우 멈추지만 멀티 스레드의 경우 문제가 생기더라도 새로운 스레드를 생성하여 다른 작업들을 수행할 수 있다.
어떤 작업이 연산량이 많아 블로킹 현상이 일어나는 것이 아닌 다른 스레드가 동작을 하여 사용자의 응답성이 증가합니다.
에러가 발생할시 싱글스레드는 처리하지 못하는 경우 멈추지만 멀티 스레드의 경우 새로운 스레드를 생성하여 극복이 가능하다
스레드 간의 context switching은 더 빠르다. 프로세서의 context switching에 비해서!
멀티 스레드 모델은 프로그래밍 난이도가 높다.
오히려 새로운 스레드 생성, context switching을 통해서 시간이 더 많이 소요되기도 한다.
자바스크립트 런타임이란 자바스크립트가 동작하는 환경을 의미합니다. 여기서 런타임이란 어떤 프로그래밍 언어가 동작하는 환경을 의미합니다. 바로위의 사진은 JS언어가 동작하는 브라우저의 런타임 도식도 입니다. 자바스크립트의 런타입의 종류로는 대표적으로 브라우저와 Node.js가 있습니다.
사진에 적혀있는 큰 틀부터 확인해 보겠습니다.
JS 엔진에 해당하는 부분입니다. JS엔진은 크롬에서 v8엔진을 사용하고 있으며
V8 엔진에는 Memory Heap 부분봐 Call Stack으로 이루어져 있습니다.
Memory Heap 부분은 변수와 객체의 메모리 할당에 사용되는 영역입니다.
Call Stack이 중요한데 코드를 읽고 실행시키는 영역입니다. 이름에 Stack이 포함되어 있듯 Stack 처럼 LIFO로 동작을 수행합니다.
즉 JS 런타임에서는 Call Stack이 하나뿐이며 JS는 싱글 스레드 임을 확인할 수 있습니다.
여기서 잠깐 그러면 Call back 함수나 비동기는 어떻게 하는거야?
위와 같은 물음 이 생길 수 있습니다! 아래에서 더 설명을 해드리도록 하겠습니다.
위의 내용을 다시 정리해봅시다.
JS는 싱글스레드지만 멀티 스레드와 같이 Call back 함수나 비동기를 처리할 수 있다는 것입니다.
과연 그 방법은 어떻게 될까요? 바로 아래 정답이 있습니다!
Web APIs 와 Event Loop , Callback Queue 를 통한 비동기 처리
조금 생소한 단어들이 눈에 보입니다.
Web APIs 는 Dom , AJAX , Timeout 관련 처리를 담당합니다.
예를들어 onclick 함수의 경우 callback함수를 webAPI에서 가지고 있다가
Click 액션이 발생한다면 바로 Task Queue에 담습니다.
Event Loop에서 Call Stack이 비어 있는지 확인하고 Task Queue의 콜백함수들을 Call Stack에 추가합니다.
결국 확인해보자면 모든 동작은 하나의 스레드로 동작하는 것을 확인할 수 있습니다. 모든 과정은 결국 Call Stack에 쌓여서 처리되기 때문입니다.
멀티 스레드와 같이 동시에 비동기로 진행되는것 처럼 보이지만 실상은 하나의 call stack에서 처리되는 것입니다!
사실 테스크 큐는 이름처럼 그냥 queue는 아닙니다! 우선순위를 가지고 있는데 이를통해 우선순위 큐 처럼 동작을 하기도 합니다.
마이크로 테스크 큐는 테스크 큐의 동작들을 event loop가 실행시키기 전에 반드시 먼저 확인하고 모두 실행시킵니다.
따라서 태스크 큐보다는 마이크로 테스크 큐가 더 우선순위가 높다고 생각할 수 있습니다.
대표적으로 마이크로 테스크 큐에서 처리하는 것은 Promise 값들입니다.
queueMicrotask()를 사용하게 된다면 event loop를 통해서 실행되는 테스크 큐에 포함된 테스크들 보다 우선순위를 높게 설정하여 먼저 처리할 수도 있습니다.
// call back 순서에 대한 재미있는 문제
let callback = () => log("일반 타임아웃 콜백을 실행했습니다.");
let urgentCallback = () => log("*** 앗! 긴급 콜백을 실행했습니다!");
log("주 프로그램 시작");
setTimeout(callback, 0);
queueMicrotask(urgentCallback); // micro task queue에 포함되어 우선함
log("주 프로그램 종료");
// 주 프로그램 시작
// *** 앗! 긴급 콜백을 실행했습니다!
// 일반 타임아웃 콜백을 실행했습니다.
// 주 프로그램 종료
블로킹은 Node.js 프로세스에서 추가적인 Javascript 의 실행을 위해서 Javascript가 아닌 작업이 완료가 될때 까지, 기다려야만 하는 상황을 의미합니다.
일반적으로 동기적이고 싱글스레드인 경우 하나의 동작이 지나치게 오래 걸리는 경우 이때를 블로킹이라고 말합니다.
하지만 JS는 자바스크립트 런타임의 Web Apis , Event Loop, task queue 등과 같은 요소로 인해 비동기적인 특성을 가지게 되었으며 논 블라킹 하게 동작할 수 있습니다.
따라서 우리는 JS는 Single thread 지만 논 블라킹이라고 말할수 있습니다!