싱글스레드와 이벤트루프
8/12/2022
작성자 : 홍원배
” 자바스크립트의 런타임환경은 싱글 쓰레드 기반이며 논 블로킹 방식의 비동기적인 동시성 언어이며 콜 스택, 이벤트 루프와 콜백 큐 그리고 여러가지 다른 API들을 가지고 있다. “
싱글스레드이기 때문에 자바스크립트 엔진은 하나의 콜스택을 가지고 작업을 수행한다. 그렇기에 콜스택에 연산이 오래 걸리면 UI가 멈출 우려가 있다.
- 스레드는 하나의 스택을 가진다 (코드, 데이타, 힙은 공유)
- ?? 자바스크립트의 실행맥락이 없을 때 (정확하게는 콜스택과 마이크로 태스크 큐가 비어져 있을때) 렌더링 엔진이 자바스크립트 엔진으로부터 제어권을 넘겨받아(렌더링 시퀀스라 표현되기도 한다) 화면에 그려준다. 렌더링 엔진은 콜스택이 비어지는 잠깐 잠깐 제어권을 받아 화면을 그리는 것이다. 그렇기에 스택에 작업이 많아 블록킹 되면 UI가 렉을 먹을 수도 있는 것이다
태스크 큐
작업 큐는 마이크로 태스크큐, 애니메이션 테스크 큐, 매크로 태스크큐 등으로 나뉘며,
- 마이크로 태스크 큐는 콜 스택이 비어있는 경우 최우선으로 작업되어 일괄적으로 하나씩 콜스택에 쌓이며 큐가 비어질때까지 실행된다. (새로 추가되는 것까지 다.. 최우선!)
- 애니메이션 태스크 큐는 마이크로 태스크 큐 다음으로 이벤트 루프에게 실행되며 후에 렌더링 엔진과 함께 동작하며 (렌더트리-리플로우-리페인트) 화면을 그려준다
- 매크로 태스크 큐는 콜 스택이 비어져 있는 경우 한번에 하나만 콜스택에 담겨 실행되고 다음 루프로 전환된다
이벤트 루프는 Microtask Queue나 Animation Frames를 방문할 때는, 큐 안에 있는 모든 작업들을 수행하지만, Task Queue를 방문할 때는 한 번에 하나의 작업만 call stack으로 전달하고 다른 Queue를 순회한다.
그럼 어떻게 멀티스레드로 작동할 수 있는가?
- WebAPI를 통해 브라우저에게 비동기적 작업을 위(일)임
// setTimeout의 경우 setTimeout(() => console.log('안녕하세요 원배님', 1000); 1. 콜스택에 setTimeout이 쌓인다 2. 실행되면서 id를 콜백함수로 등록해 놓고 WebAPI를 통해 브라우저에게 일임 3. 1000ms가 지나면 매크로 태스크 큐에 쌓이고 콜스택이 비어져있을 때 다시 실행된다
… 그래서 fetch() 함수 자체는 자바스크립트 엔진과 별개로 동작하기 때문에 어쩌면 multi Thread 형식처럼 보일 수 있다.그러나 Javascript 자체가 Single Thread로 동작하다 보니, 서버에 요청하고 응답을 받는지에 대한 Tracking을 여전히 진행하게 된다. 이는 싱글 스레드의 리소스를 분명히 쓰고 있으며 성능에 영향을 끼치고 있음은 확실하다. Javascript가 훌륭한 Event Loop라는 요소로 비동기 로직 처리를 잘 제어하고 있지만, 여전히 싱글 스레드의 한계를 가지고 있을 수밖에 없는 요소이다.
⇒ 비동기 처리를 위한 기본구성이며 멀티스레드 인척 하는 것이다
- WebWorker를 통해서 백그라운드에서 원할하게 비동기 작업을 수행
- WebWorker를 사용해서 비동기 함수에 worker 쓰레드를 하나씩 직접 할당해서 사용할 수 있다
- 비동기 작업이 오래 걸리는 작업인 경우 WebWorker을 사용해서 작업을 수행할 수 있다
- ServiceWorker는 브라우저창이 꺼져도 실행되며, cors와 https에서만 작동가능한 제약조건들이 있다
비동기의 처리 방법
- 콜백함수
- Promise 객체를 이용 + then
Promise객체는 약속된 결과값이다. 결과의 상태가 fulfilled로 이행되어져을 때 then을 통해 promise가 전달되고 콜백함수처럼 결과를 핸들링해나갈 수 있다
- 비동기 함수 (async함수)
- ES6(ES2015)에서 처음 나온 개념
- 비동기적인 작동을 하는 fetch 등과 함께 비동기식 코드를 동기식으로 작성할 수 있다
- promise객체를 이용한다
비동기 함수(async함수)는 비동기적으로 실행되는 함수 (ex setTimeout)와 다른 개념이지만 묶여서 비동기 함수라 불러지기도 한다
콜스택에 재귀함수가 무한정 쌓이게 되면
Maximum call stack size exceeded
오류 발생스크롤을 하게 되면 어떤 일이?
콜스택에 엄청 쌓이게 되고 성능에 영향을 주겠지, 디바운싱이 필요하다.
이벤트 루프란?
콜스택이 비어져있을 때 태스크 큐에 있는 콜백함수들을 불러와서 콜스택에 넣어주는 브라우저의 동작장치 (JS엔진 기능이 아니라 런타임환경 기능)
출처
쓰레드란?
매크로태스크큐 마이크로태스크큐?
브라우저 런타임?
웹워커에 대한 제로초의 관점
ZeroCho : 저도 엄청나게 많이 검색했는데도 헷갈리더라고요. 결국 운영체제 대충 공부한 후에 깨닫게 되었습니다. 비동기더라도 싱글 쓰레드에서는 싱글 쓰레드가 결국 모든 것을 처리해야 하기 때문에 아무런 시간 이득이 없다는 것을요. 다만 네트워크나 파일시스템 접근만 그 시간동안 다른 작업을 할 수 있어서 시간 이득이 있습니다.
익명 : 답변 감사합니다! 그럼 하나 더 궁금한게 있습니다. 비동기라 함은 단순히 작업의 우선순위를 뒤로 미루는 것인가요? 싱글쓰레드가 메인 로직 작업을 하는 동안 비동기 작업은 누가 언제 어디서 하고 있는 건가요? 태스크 큐에는 콜백함수만 넣어진다고 알고 있습니다. 그럼 비동기 함수 자체내에서 콜백을 태스크 큐에 넣기 전에 다른 로직을 수행중이라면 이 로직을 누가 실행하는지가 궁금합니다. 브라우저나 node 엔진 자체에서 다른 쓰레드가 활용되고 있는 거라면 사실상 비동기나 웹워커나 큰 시간차가 없지 않을까요 ?
ZeroCho : 네 작업의 우선 순위를 뒤로 미루는 겁니다. 병렬로 실행되는 게 아니라요. 로직(사람이 직접 작성한 코드)(async await, observerAPI, process.nextTick())은 모두 메인쓰레드에서 처리됩니다. 네트워크 요청이나 파일시스템 정도만 운영체제에서 멀티쓰레드로 처리해주고요. 따라서 그 이외의 메인 쓰레드 로직(위에서 sleep 함수같은)이 오래 걸리면 비동기라 하더라도 시간이 오래 소요됩니다. 웹워커는 아예 다른 쓰레드를 생성해 로직을 거기서 처리하는거라서 속도에 이득을 볼 수 있습니다.
싱글코어인 경우 워커를 사용하더라도 이득을 못 보는 것도 마찬가지로 설명가능합니다. 멀티쓰레드를 돌려도 코어가 하나라서 코어에서 쓰레드 전환 작업을 해서 한 번에 하나의 쓰레드만 처리할 수 있기 때문입니다.