싱글 쓰레드와 이벤트 루프

조양권·2021년 5월 18일
0

JS

목록 보기
3/17

앞선 블로그에 js의 구동방식에 관해 서술한 적이 있다. 해당 블로그를 작성하며 싱글 쓰레드(‘single thread’)와 이벤트루프(‘event loop’)에 관한 내용을 적는다.

싱글 쓰레드

우선 쓰레드라 함은 프로세스 내에서 실제로 작업을 수행하는 주체를 의미한다. 싱글 쓰레드는 하나의 쓰레드를 같는 프로세스이고, 멀티 쓰레드는 하나 이상의 쓰레드를 같는 프로세스이다.

싱글 쓰레드와 멀티 쓰레드의 차이

싱글 쓰레드는 하나의 작업이 끝나야 다음 작업을 시작한다. 멀티 쓰레드는 두개의 작업을 짧은 시간동안 번갈아 가며 수행한다. 이런 작업은 마치 두개의 작업이 동시에 이루어지는 것 처럼 보이게 한다.

하지만 두 방식간 실제 작업시간 차이는 크게 없는 편인데, 오히려 멀티 쓰레드가 시간이 조금 더 오래걸리는 경우가 있다. 이유는 쓰레드간의 작업전환이 시간이 걸리기 때문이다. 쓰레드 혹은 프로세스간의 작업전환을 컨텍스트 스위칭(‘context switching’)이라고 한다. 추가적으로 쓰레드간의 스위칭 보다 프로세스 간의 스위칭이 더 많은 정보를 저장해야 하므로 더 많은 시간이 소요된다.

이러한 멀티 쓰레드는 cpu이외의 자원을 사용하는 경우 싱글 쓰레드 보다 효율적이다.

이벤트 루프

그렇다면 이벤트 루프란 무엇일까. JS는 싱글 쓰레드 기반의 언어이다. 하지만 실제로 JS가 사용되는 환경을 보면 마치 여러개의 작업이 동시에 진행되는 것 처럼 보인다. 예컨데 브라우저에서 애니메이션이 작동하면서 마우스클릭으로 정보를 받아 진행하는 것과 같이 말이다. 혹은 동시에 여러개의 HTTP요청을 수행해 내기도 한다. JS는 어떻게 동시성을 지원하는 걸까?

function delay() {
    for (var i = 0; i < 100000; i++);
}
function horde() {
    delay();
    console.log('horde!');
}
function is() {
    delay();
    console.log('is!');
}
function nothing() {
    delay();
    console.log('nothing!');
}
setTimeout(horde, 10);
setTimeout(is, 10);
setTimeout(nothing, 10);

위의 코드는 순차적으로 ‘horde’, ‘is’, ‘nothing’을 찍어 줄 것이다. 정확히는 지연없이 setTimeout이 순차적으로 3번 호출되고 나오는 과정을 거친 이후에 호출스택이 비워지게된다. 그리고 10ms가 지나고나서 ‘horde’, ‘is’, ‘nothing’을 출력하게 된다.

(Run-to-Completion 방식으로 진행되는 JS구동방식)

(setTimeout은 브라우저에게 타이머 이벤트를 요청한 뒤 스택에서 제거된다.)

첫번째 ‘horde’가 출력되고 난 뒤, setTimeout으로 넘긴 console.log(‘is’)는 ‘태스크 큐(‘task queue’)에서 대기된다. 태스크 큐는 콜백함수들이 대기하고 있는 큐 형태의 배열이라 할 수 있다. 그리고 이벤트 루프는 호출스택이 비워질 때마다 태스크 큐에서 대기하고 있는 콜백함수들을 꺼내와서 실행해 주는 역할을 하고있다.

그러면 다시 위의 코드가 진행되는 과정을 상세히 설명하자면 먼저 setTimeout이 지연없이 3번 호출되고 닫게된다. 이벤트 루프는 horde함수를 태스크 큐에 넣고, 현재 호출스택이 비어있기 때문에 바로 호출스택으로 보낸다. 그리고 horde함수가 실행되면서 horde가 찍힘과 동시에 이벤트루프는 is함수를 태스크큐에 집어 넣는다. horde가 찍히면서 horde함수가 종료됨과 동시에 이벤트 루프는 is함수를 호출스택으로 보낸다. 마찬가지로 is함수가 호출되면서 nothing함수는 태스크 큐로 가게된다. is가 출력됨으로써 is함수는 종료되고 태스크 큐에 있던 nothing함수는 이벤트루프에 의해 호출스택으로 보내지게 된다.

이벤트 루프는 이런 식으로 현재 실행중인 태스크가 있는지, 그리고 태스크 큐에 태스크가 있는지를 반복적으로 확인하기에 루프라는 이름이 붙었다.

모든 비동기 API들은 작업이 완료되면 콜백 함수를 태스크 큐에 추가하며, 이벤트 루프는 현재 실행중인 태스크가 없을 때(주로 호출 스택이 비워졌을때) 태스크 큐의 첫 번째 태스크를 꺼내와 실행한다.

profile
할 수 있는 것이 늘어나는 즐거움

0개의 댓글