이벤트 루프는 자바스크립트와 같은 단일 스레드 환경에서 비동기 작업을 효율적으로 처리하기 위한 핵심 메커니즘이다. 쉽게 말해, 마치 교통 정리와 같이 작업들이 순서대로 처리될 수 있도록 관리하는 역할을 한다.
동기 vs 비동기
동기와 비동기를 나누는 핵심 기준은 '작업의 완료와 대기'이다.
동기는 특정 작업이 있다면 무조건 순서대로 진행한다. 작업 A와 B가 있다면 무조건 작업 A가 완료되야 작업 B를 진행한다. A가 오래걸리거나 아예 작업이 멈춰있어도 B를 진행할 수 없다. A가 완료 될때까지 무조건 기다린다.
하지만
비동기는 작업 A의 완료를 기다리지 않고 작업 B를 진행할 수 있다. 작업 A가 오래걸리든 멈춰있든, 아주 빠르든 아무런 관계 없이 작업 B를 진행할 수 있다.
때문에 동기는 파일을 다운로드 받을 때 순서대로 다운로드 받는 것과 비슷해보이고 비동기는 병렬 다운로드로 한번에 여러 파일을 다운로드 받는 것과 비슷해보인다. 하지만 결정적인 차이가 있다.
비동기 작업은 여러작업을 동시에 진행하는 것이 아니라 '다른 작업의 완료를 기다리지 않고' 또 다른 작업을 진행하는 것이라는 점이다.
때문에 파일 A를 다운로드하다 멈추고 B를 다운로드 하다 다시 멈추고 A를 다운로드 하는 식이어도 비동기 작업이며, 이 전환 과정이 1시간씩 걸린다고 해도 비동기 작업이다.
다운로드로 비유한 예시
- 동기 (Synchronous)
- 1GB 파일을 다운로드할 때, 900MB 부분을 먼저 다운로드하고 완전히 다운로드될 때까지 기다린다.
- 900MB 다운로드가 완료된 후에만 100MB 부분을 다운로드할 수 있다.
- 만약 900MB 다운로드에 시간이 오래 걸린다면, 100MB 다운로드는 그만큼 더 늦어진다.
- 즉, 전체 1GB 다운로드가 완료될 때까지 다른 어떤 작업도 할 수 없다.
- 비동기 (Asynchronous)
- 1GB 파일을 다운로드할 때, 900MB 부분과 100MB 부분을 동시에 다운로드하기 시작한다.
- 100MB 다운로드가 900MB 다운로드보다 먼저 완료되더라도, 900MB 다운로드가 완료될 때까지 기다릴 필요가 없다.
- 900MB 다운로드와 100MB 다운로드는 서로 독립적으로 진행되며, 다운로드 완료 시점에 결과를 처리한다.
- 즉, 다운로드 진행 중에도 다른 작업을 수행할 수 있다.
완전히 동기, 비동기라고 할 수는 없지만 대략적으로 이런 느낌이다.
학년을 통한 동기의 예시
예를 들어 학교를 다닐 경우 일반적으로 학년은 순차적으로 올라가야 한다. 1학년을 마친 다음에 2학년으로 올라가게 된다. 이런 방식을 동기 방식이라고 할 수 있다.
- 1학년
- 2학년
- 3학년
- 졸업
문제집을 통한 동기의 예시
하지만 문제집을 풀 때 국어, 수학, 영어 문제집이 각각 1권씩 있다고 할 때, 국어 문제집을 풀다가 언제든 수학 문제집을 풀 수 있다. 굳이 국어 문제집을 다 풀고 나서 수학 문제집을 풀 필요는 없다. 이런 방식을 비동기라고 할 수 있다.
- 국어 공부
- 수학 공부
- 영어 공부
구분 | 동기 (Synchronous) |
비동기 (Asynchronous)
|
실행 순서 | 순차적 |
동시에 또는 병렬적
|
작업 완료 대기 | 이전 작업 완료까지 대기 |
대기하지 않고 다음 작업 진행
|
결과 처리 | 즉시 처리 |
나중에(콜백, 프로미스 등) 처리
|
시스템 효율성 | 지연 발생 가능성 높음 |
지연 최소화, 효율성 높음
|
동기의 장점
그럼 뭐 굳이 동기 작업을 할 이유가 전혀 없지 않냐고 할 수도 있다.
하지만 동기의 장점이 없는 것은 아니다.
- 단순성:
- 동기 코드는 순차적으로 실행되므로 코드의 흐름을 이해하고 디버깅하기 쉽다.
- 비동기 코드는 콜백 함수나 프로미스 등으로 인해 코드의 흐름이 복잡해질 수 있다.
- 예측 가능성:
- 동기 코드는 실행 순서가 명확하게 보장되므로, 코드의 실행 결과를 예측하기 쉽다.
- 비동기 코드는 작업 완료 순서가 보장되지 않으므로, 실행 결과를 예측하기 어려울 수 있다.
- 데이터 동기화:
- 동기 코드는 작업 결과를 즉시 사용할 수 있으므로, 데이터 동기화 문제를 쉽게 해결할 수 있다.
- 비동기 코드에서는 작업 완료 시점을 알 수 없으므로, 데이터 동기화 처리가 복잡해질 수 있다.
- 단순한 작업 처리:
- 시간이 오래 걸리지 않는 단순한 작업을 처리할 때는 동기 방식이 더 효율적일 수 있다.
- 비동기 방식은 작업 시작과 완료 처리 과정에서 추가적인 오버헤드가 발생할 수 있다.
동기가 적합한 상황:
- 단순한 스크립트:
- 간단한 스크립트나 배치 작업을 처리할 때는 동기 방식이 더 적합할 수 있다.
- 실시간 시스템:
- 실시간으로 데이터를 처리해야 하는 시스템에서는 동기 방식이 더 적합할 수 있다.
- 비동기 방식은 작업 완료 시점을 예측할 수 없기 때문에 실시간 처리에 적합하지 않다.
- UI 프로그래밍:
- UI 프로그래밍에서는 사용자 입력을 즉시 처리해야 하는 경우가 많으므로 동기 방식이 더 적합할 수 있다.
예를 들면 레이아웃을 표시하는 코드와 내용물의 글씨를 표시하는 코드가 분리되어 있다고 하자.
그런데 이걸 레이아웃 크기 늘리기, 글씨 늘리기 순으로 하면 깔끔하게 진행이 되겠지만
비동기 식으로 레이아웃 크기 늘리다가 글씨 1개 크기 늘리다가 다시 레이아웃 크기 조정 식으로 하면 화면이 무척 복잡하게 바뀔 것이다.
이벤트 루프
다시 이벤트 루프 얘기로 돌아와서 자바 스크립트의 엔진은 단일 스레드 환경에서 작동한다.
자바스크립트가 단일 스레드를 사용하는 이유는 주로 웹 브라우저 환경에서의 UI 안정성과 개발의 단순성 때문이다. 단일 스레드 환경은 여러 스레드가 동시에 UI를 변경하여 발생할 수 있는 데이터 불일치 및 충돌 문제를 방지하고, 코드 작성 및 관리를 단순하게 만들어준다.
이런 장점들이 프로그래밍 언어 중 자바스크립트의 점유율이 높은 이유일 것이다.
하지만 단일 스레드 환경에서는 시간이 오래 걸리는 작업(I/O 작업, 네트워크 요청 등)을 동기적으로 처리할 경우, 전체 프로그램이 멈추는 블로킹 현상이 발생할 수 있다.
블로킹 현상이란?
블로킹(Blocking) 현상은 프로그램의 실행 흐름이 특정 작업 때문에 일시적으로 멈추는 현상을 의미한다.
특히 단일 스레드 환경에서 시간이 오래 걸리는 작업(I/O 작업, 네트워크 요청 등)을 동기적으로 처리할 때 발생한다.
블로킹 현상의 작동 방식:
- 동기적 작업 실행:
- 프로그램이 특정 작업을 요청하고, 해당 작업이 완료될 때까지 기다린다.
- 이 과정에서 프로그램은 다른 작업을 수행하지 못하고 멈춰있게 된다.
- 작업 완료 대기:
- 시간이 오래 걸리는 작업이 완료될 때까지 프로그램은 아무것도 하지 못하고 기다린다.
- 예를 들어, 파일 다운로드, 데이터베이스 쿼리, 네트워크 요청 등이 있다.
- 프로그램 멈춤:
- 프로그램의 실행 흐름이 멈추고, 사용자 입력이나 다른 이벤트에 반응하지 못하는 상태가 된다.
- 이러한 현상은 사용자 경험을 크게 저해할 수 있다.
블로킹 현상의 예시:
- 파일 다운로드 중 UI 멈춤
- 웹 브라우저에서 큰 파일을 다운로드하는 동안 브라우저 UI가 멈추고, 사용자의 클릭이나 스크롤에 반응하지 않는 경우가 있다.
- 데이터베이스 쿼리 중 응답 없음
- 웹 서버가 데이터베이스에 복잡한 쿼리를 보내고 응답을 기다리는 동안, 다른 사용자 요청에 응답하지 못하는 경우가 있다.
이러한 단점을 극복하고 높은 응답성을 유지하기 위해 자바스크립트는 이벤트 루프라는 메커니즘을 도입했다.
이벤트 루프는 비동기 작업을 효율적으로 처리하고, 콜백 큐를 통해 작업 완료 시 콜백 함수를 실행한다.
자바스크립트 엔진은 동기 작업을 실행하고, 비동기 작업은 실행 환경(브라우저 또는 Node.js)의 도움을 받아 처리하며, 이벤트 루프를 통해 관리된다.
이를 통해 자바스크립트는 단일 스레드 환경에서도 높은 성능과 응답성을 유지할 수 있다.
이벤트 루프의 작동 방식
- 콜 스택(호출 스택)에서 동기적인 코드들을 처리한다.
- 비동기 작업이 완료되면 관련 콜백 함수를 콜백 큐에 넣는다.
- 이벤트 루프는 호출 스택이 비어 있는지 확인한다.
- 호출 스택이 비어 있으면 콜백 큐의 첫 번째 콜백 함수를 호출 스택으로 이동시켜 실행한다.
- 3, 4번 과정을 반복한다.
1. 콜 스택 (Call Stack)
- 현재 실행 중인 함수들이 쌓이는 곳이다.
- 함수가 호출되면 스택에 추가되고, 실행이 끝나면 스택에서 제거된다.
- 자바스크립트는 단일 스레드이므로 한 번에 하나의 작업만 처리할 수 있다.
자바스크립트의 메모리를 설명할 때 나오는 함수 호출과 지역 변수, 원시 타입 데이터가 저장되는 콜 스택과 동일한 장소다. 즉, 자바스크립트의 코드들은 기본적으로 콜 스택 영역에서 처리된다.
2. 웹 API 처리
- 웹 API는 별도의 스레드에서 해당 비동기 작업을 처리한다.
- 예를 들어, setTimeout은 타이머를 설정하고, fetch는 네트워크 요청을 보낸다.
3. 콜백 큐 (Callback Queue)
- 비동기 작업(예: setTimeout, 네트워크 요청)의 완료 후 실행될 콜백 함수들이 대기하는 곳이다.
- 비동기 작업이 완료되면 해당 콜백 함수가 큐에 추가된다.
4. 이벤트 루프 (Event Loop)
- 호출 스택이 비어있는지 지속적으로 확인한다.
- 호출 스택이 비어있으면 콜백 큐에서 첫 번째 콜백 함수를 꺼내 호출 스택으로 이동시켜 실행한다.
- 이 과정을 반복하면서 비동기 작업의 결과를 순차적으로 처리한다.
console.log('Start!'); // 1. 동기적으로 실행되는 코드
setTimeout(function cb1() {
console.log('Callback 1: Inside Callback'); // 4. 콜백 큐에서 호출 스택으로 이동하여 실행
}, 2000); // 2. 비동기 작업 (setTimeout)
setTimeout(function cb2() {
console.log('Callback 2: Inside Callback'); // 5. 콜백 큐에서 호출 스택으로 이동하여 실행
}, 1000); // 3. 비동기 작업 (setTimeout)
console.log('End!'); // 6. 동기적으로 실행되는 코드
자바스크립트는 동기 언어이기 때문에 원래대로라면 코드가 1~11줄까지 순서대로 실행되야 하지만 setTimeout()라는 메서드를 사용해서 4번과 8번째 줄에 실행되야 하는 console.log를 콜백 큐로 보냈다. 때문에 11번째 줄이 먼저 실행되고 그 다음에 8번 4번 째 줄이 실행됐다.
setTimeout() 메서드는 이름처럼 타이머를 설정하여 특정 시간 후에 콜백 함수를 실행하게 한다.
메서드
1. 타이머 관련 메서드 (매크로태스크)
- setTimeout(callback, delay)
- 주어진 delay(밀리초) 이후에 callback 함수를 실행한다.
- 예시: setTimeout(function() { console.log("3초 후 실행"); }, 3000);
- setInterval(callback, delay)
- 주어진 delay 간격으로 callback 함수를 반복적으로 실행한다.
- 예시: setInterval(function() { console.log("1초마다 실행"); }, 1000);
- requestAnimationFrame(callback)
- 브라우저가 다음 프레임을 렌더링하기 전에 callback 함수를 실행한다.
- 주로 애니메이션과 같이 부드러운 화면 업데이트가 필요할 때 사용된다.
-
function animate(timestamp) { console.log("애니메이션 프레임:", timestamp); requestAnimationFrame(animate); } requestAnimationFrame(animate);
2. Promise 관련 메서드 (마이크로태스크)
- Promise.prototype.then(callback):
- Promise가 성공(resolve)했을 때 callback 함수를 실행한다.
-
new Promise(resolve => resolve("성공")) .then(result => console.log(result));
- Promise.prototype.catch(callback)
- Promise가 실패(reject)했을 때 callback 함수를 실행한다.
-
new Promise((resolve, reject) => reject("실패")) .catch(error => console.error(error));
- Promise.prototype.finally(callback)
- Promise의 성공 또는 실패 여부와 관계없이 callback 함수를 실행한다.
-
new Promise((resolve, reject) => resolve("성공")) .finally(() => console.log("완료"));
- queueMicrotask(callback)
- 마이크로태스크 큐에 callback 함수를 추가한다. promise then, catch, finally 와 유사하게 마이크로태스크 큐에 작업을 넣는 역할을 한다.
3. Node.js 환경에서 추가적으로 사용되는 메서드
- setImmediate(callback)
- I/O 이벤트 이후에 callback 함수를 실행한다.
- 매크로태스크 큐에 등록이 된다.
-
setImmediate(() => { console.log("immediate 실행"); });
중요성
- 논블로킹 (Non-blocking)
- 시간이 오래 걸리는 작업(I/O 작업, 네트워크 요청 등)을 동기적으로 처리하면 전체 프로그램이 멈추는 블로킹 현상이 발생한다.
- 이벤트 루프는 이러한 작업을 비동기적으로 처리하여 메인 스레드가 다른 작업을 계속 수행할 수 있도록 한다.
- 높은 응답성 유지:
- 웹 브라우저나 Node.js와 같은 환경에서 사용자 인터페이스의 응답성을 유지하는 데 필수적이다.
- 이벤트 루프를 통해 메인 스레드는 사용자 이벤트(클릭, 스크롤 등)를 즉시 처리하고, 시간이 오래 걸리는 작업은 백그라운드에서 처리할 수 있다.
- 동시성 (Concurrency) 처리:
- 싱글 스레드에서 마치 여러 작업을 동시에 처리하는 것처럼 보이게 한다.
'자바 스크립트(java script) > 자바 스크립트 기초' 카테고리의 다른 글
얕은 비교, 깊은 비교 (0) | 2025.03.24 |
---|---|
클로저(closure) (0) | 2025.03.24 |
이벤트 위임(Event Delegation) (0) | 2025.03.22 |
이벤트 버블링 (Event Bubbling)과 캡처링 (Event Capturing) (0) | 2025.03.21 |
마치 미어캣 같은 이벤트 리스너, 이벤트 핸들러 (0) | 2025.03.21 |