프로그래밍을 처음 접하는 입문자들이 흔히 가지는 가장 큰 오해 중 하나는 "코드는 항상 위에서 아래로, 작성된 순서대로 실행된다"는 생각입니다. 단순하고 짧은 코드에서는 이 말이 사실일 수 있습니다. 하지만 네트워크 요청, 버튼 클릭, 타이머와 같은 기능이 등장하는 순간, 이러한 위에서 아래로의 직선적인 실행 흐름은 완전히 깨지게 됩니다.
이 글에서는 왜 나중에 작성된 코드가 먼저 실행되는 현상이 발생하는지, 그리고 웹 개발의 핵심인 비동기(Asynchronous) 처리와 이벤트 루프(Event Loop) 의 구조에 대해 확실하게 정리해 보겠습니다.
--------------------------------------------------------------------------------
1. 코드는 무조건 위에서 아래로 실행된다? (동기 실행의 한계)
우리가 처음 배우는 프로그래밍의 실행 방식은 주로 동기(Synchronous) 방식입니다. 동기 방식은 한 줄의 코드 실행이 완전히 끝나야만 다음 줄의 코드가 실행되는 구조입니다. 즉, 작업이 끝날 때까지 다음 작업은 멈춰서 기다려야만 합니다.
이를 일상생활에 비유하자면 '은행 창구' 와 같습니다. 은행 창구에서 직원이 한 사람씩 업무를 처리하듯, 앞사람의 업무 처리가 오래 걸리면 뒷사람들은 하염없이 대기해야 합니다. 웹 브라우저에서도 마찬가지입니다. 자바스크립트는 싱글 스레드(Single-thread) 기반이기 때문에 한 번에 하나의 작업만 수행할 수 있습니다. 만약 네트워크 요청이나 파일 읽기처럼 오래 걸리는 작업이 동기 방식으로 실행된다면, 그 작업이 끝날 때까지 프로그램 전체가 멈추게 되고 사용자 입장에서는 화면이 멈추거나 버벅이는 것으로 느끼게 됩니다.
--------------------------------------------------------------------------------
2. 프로그램이 멈추는 것을 막는 마법, 비동기(Asynchronous) 실행
이러한 동기 방식의 문제를 해결하기 위해 등장한 것이 바로 비동기(Asynchronous) 실행입니다. 비동기 방식은 앞선 작업이 끝나기를 기다리지 않고, 다음 작업을 먼저 수행하는 방식입니다. 시간이 오래 걸리는 작업은 따로 맡겨두고, 프로그램은 멈춤 없이 계속 다음 코드를 진행합니다.
이것은 '식당에서의 음식 주문' 에 비유할 수 있습니다. 손님이 음식을 주문하면 종업원은 주방에 주문을 넣고(요청 시작), 음식이 나올 때까지 손님 테이블 앞에서 기다리지 않습니다. 그동안 다른 테이블의 주문을 받거나 서빙을 하고, 요리가 완성되면 그때 다시 손님에게 음식을 가져다줍니다(결과 처리).
동기 방식은 함수를 호출한 쪽(caller)이 결과를 끝까지 기다리며 흐름을 통제하지만, 비동기 방식은 함수를 넘기고 맡기는 흐름을 가지며 콜백(callback)을 통해 결과를 나중에 전달받습니다.
--------------------------------------------------------------------------------
3. 이 모든 걸 조율하는 핵심 시스템: 이벤트 루프(Event Loop)
그렇다면 한 번에 하나의 일만 할 수 있는 싱글 스레드 언어인 자바스크립트가 어떻게 비동기 작업을 동시에 처리하는 것처럼 보일까요? 그 비밀은 브라우저 내부의 이벤트 루프(Event Loop) 와 Web API 에 있습니다.
실제로 오래 걸리는 타이머나 네트워크 요청(fetch) 등의 작업은 자바스크립트 엔진이 아닌 브라우저가 제공하는 Web API(멀티 스레드)가 대신 처리해 줍니다. 과정은 다음과 같습니다.
- 자바스크립트의 실행 창구인 '콜 스택(Call Stack)'에 비동기 함수가 들어오면, 이를 Web API로 넘깁니다.
- Web API에서 작업이 완료되면, 실행해야 할 콜백 함수를 대기열인 '태스크 큐(Task Queue)'로 보냅니다.
- 이때 이벤트 루프 가 등장합니다. 이벤트 루프는 콜 스택이 비어있는지 계속 확인하다가, 스택이 비워지면 태스크 큐에 대기 중인 콜백 함수를 콜 스택으로 밀어 넣어 실행시킵니다.
이벤트 루프는 마치 '콜센터 관리자' 나 '택배 물류 센터' 와 같습니다. 요청을 접수해 두고, 처리가 완료된 것들만 큐(Queue)에서 꺼내어 순서대로 다시 작업할 수 있도록 끊임없이 조율합니다.
--------------------------------------------------------------------------------
4. 나중에 쓴 코드가 먼저 실행되는 이유 (실행 순서의 역전)
비동기와 이벤트 루프의 개념을 이해했다면, 왜 코드의 실행 순서가 뒤바뀌는지 알 수 있습니다. 코드 작성 순서가 곧 실행 순서를 의미하지는 않습니다.
예를 들어, 데이터를 요청하는 코드(1) 다음에 로그를 출력하는 코드(2)를 작성했다고 가정해 보겠습니다. 데이터를 요청하는 코드는 비동기로 동작하여 Web API와 태스크 큐를 거쳐야 하므로, 상대적으로 처리가 빠른 로그 출력 코드(2)가 먼저 실행됩니다. 이는 마치 편지를 우체통에 넣고(요청), 바로 다음 할 일을 하다가(다른 작업 실행), 며칠 뒤에 답장을 받는(결과 나중에 처리) 것과 같은 이치입니다.
특히 큐(Queue) 중에서도 Promise와 같이 우선순위가 높은 작업은 '마이크로태스크 큐(Microtask Queue)'에 담기며, 이는 setTimeout 등이 담기는 일반 태스크 큐보다 항상 먼저 처리됩니다. 따라서 같은 비동기 코드라 하더라도 종류에 따라 우선순위가 달라져 실행 순서가 역전될 수 있습니다.
--------------------------------------------------------------------------------
5. 요약 및 마무리 : 이벤트 루프, 왜 알아야 할까?
입문자들은 흔히 코드가 항상 위에서 아래로만 실행된다고 오해하거나, 이벤트 루프를 어렵고 복잡한 내부 기술로만 치부하여 무시하곤 합니다.
하지만 이 이벤트 기반(Event Driven) 흐름 을 명확히 이해하면 얻을 수 있는 장점은 무수히 많습니다.
- 단순한 직선 사고에서 벗어나 '멀티 작업 관리 사고' 로 프로그램을 바라볼 수 있게 됩니다.
- 자바스크립트나 파이썬의 async/await 코드를 훨씬 수월하게 이해할 수 있습니다.
- 원인을 알 수 없었던 비동기 버그나 무한 루프 에러의 원인을 구조적으로 파악하고 빠르게 해결할 수 있습니다.
결론적으로 프로그램은 단순히 위에서 아래로 텍스트를 읽듯 실행되는 것이 아닙니다. 비동기 프로그래밍 환경에서 프로그램은, 이벤트 루프라는 관리자가 조율하는 거대한 흐름 속에서 작업이 준비된 순서대로 유기적으로 실행되는 시스템이라는 점을 꼭 기억하시길 바랍니다.

반응형