[Operating Systems] 비동기 처리와 이벤트 기반 프로그래밍 기초

동기와 비동기 처리의 차이를 이해하고, 왜 비동기가 필요한지 알아보자.

그 다음 비동기 환경에서 여러 이벤트를 효율적으로 관리하는 방법인 이벤트 기반 프로그래밍을 살펴보고, 이를 구현하는 Publisher-Subscriber 패턴과 Event Emitter까지 알아보고자 한다.


🔄 동기 vs 비동기

🚶‍♂️ 동기 처리란?

동기 처리는 작업을 순서대로 하나씩 처리하는 방식이다. 한 작업이 끝나야 다음 작업을 시작할 수 있다.

프로세스가 파일을 읽거나 네트워크 통신을 할 때, 시스템 콜을 호출하면 그 작업이 완료될 때까지 프로세스는 블로킹 상태가 된다. 이때 CPU는 해당 프로세스를 대기 큐로 보내고 다른 프로세스를 실행한다.

마치 은행에서 번호표를 뽑고 순서대로 기다리는 것과 같다. 앞 사람 업무가 끝나야 내 차례가 온다.

// 동기 처리 예시
console.log("1. 첫 번째 작업");
console.log("2. 두 번째 작업");
console.log("3. 세 번째 작업");
// 1 → 2 → 3 순서대로 실행

특징

  • 예측 가능한 실행 순서
  • 이해하기 쉬움
  • 하나가 오래 걸리면 전체가 느려짐
  • I/O 대기 중에는 CPU가 다른 프로세스를 처리해야 함

🏃‍♂️ 비동기 처리란?

비동기 처리는 작업을 요청하고 결과를 기다리지 않고 다른 일을 계속하는 방식이다. 결과가 나오면 그때 처리한다.

시스템에서는 논블로킹 I/O와 인터럽트 메커니즘을 사용해서 이를 구현한다. 프로세스가 I/O 작업을 요청하면 커널이 백그라운드에서 처리하고, 프로세스는 즉시 다른 작업을 계속할 수 있다. I/O가 완료되면 하드웨어 인터럽트가 발생해서 완료를 알린다.

마치 온라인으로 주문하고 배송을 기다리는 동안 다른 일을 하는 것과 같다. 택배가 오면 그때 받으면 된다.

// 비동기 처리 예시
console.log("1. 주문 시작");
setTimeout(() => {
  console.log("3. 배송 완료!");
}, 2000);
console.log("2. 다른 일 하기");
// 실행 순서: 1 → 2 → (2초 후) 3

특징

  • 효율적인 시간 활용
  • 전체적으로 빠른 처리
  • 실행 순서 예측이 어려움
  • 인터럽트 처리가 복잡함
  • CPU 활용도 높음

⚡ 왜 비동기가 필요한가?

일상생활에서 생각해보자. 빨래를 돌리면서 설거지도 하고 청소도 한다. 빨래가 끝날 때까지 아무것도 안 하고 기다리지 않는다.

프로그램도 마찬가지다. 파일을 읽거나 네트워크 통신을 하는 동안 다른 작업을 할 수 있다면 훨씬 효율적이다.

모든 I/O를 동기적으로 처리한다면, CPU는 대부분의 시간을 아무것도 하지 않고 기다리는 데 보내게 된다. 운영체제 입장에서는 이런 낭비를 줄이기 위해 비동기 처리를 활용한다.

비동기가 유용한 상황

  • 파일 읽기/쓰기: 디스크 I/O는 CPU보다 수천 배 느림
  • 네트워크 통신: 네트워크 지연은 예측 불가능하고 매우 큼
  • 데이터베이스 조회: 복잡한 쿼리는 시간이 오래 걸림
  • 사용자 입력 대기: 언제 입력할지 알 수 없음

📡 이벤트 기반 프로그래밍

🎯 이벤트란?

이벤트는 시스템에서 어떤 중요한 일이 일어났다는 신호다. 하드웨어에서 발생하는 키보드 입력, 마우스 클릭, 타이머 만료부터 소프트웨어에서 발생하는 파일 I/O 완료, 네트워크 패킷 도착까지 모든 것이 이벤트다.

운영체제는 이런 이벤트들을 인터럽트라는 메커니즘으로 처리한다. 인터럽트가 발생하면 CPU가 현재 작업을 잠시 멈추고 인터럽트 핸들러를 실행해서 이벤트를 처리한다.

현실에서도 우리는 이벤트에 반응한다. 알람이 울리면 일어나고, 전화벨이 울리면 받고, 초인종이 울리면 문을 연다.

👂 이벤트 리스너 개념

이벤트 리스너는 특정 이벤트가 발생하기를 기다리고 있다가 반응하는 기능이다. 시스템 레벨에서는 인터럽트 핸들러가 이 역할을 한다.

마치 경비원이 CCTV를 보면서 특정 상황이 발생하면 조치를 취하는 것과 같다.

// 버튼 클릭 이벤트 리스너
button.addEventListener("click", function () {
  console.log("버튼이 클릭되었습니다!");
});

// 파일 로드 완료 이벤트 리스너
fileLoader.addEventListener("load", function () {
  console.log("파일 로드 완료!");
});

🔔 이벤트 발생과 처리 흐름

이벤트 기반 프로그래밍의 흐름은 다음과 같다.

  1. 이벤트 리스너 등록: “이런 일이 일어나면 이렇게 해줘”
  2. 이벤트 발생: 실제로 그 일이 일어남
  3. 이벤트 처리: 미리 정해둔 대로 반응

하드웨어에서 이벤트가 발생하면 인터럽트 신호가 CPU로 전달된다.

CPU는 현재 명령어를 완료한 후 인터럽트를 확인하고, 현재 프로세스의 상태를 PCB에 저장한다.

그 다음 인터럽트 벡터 테이블에서 적절한 핸들러를 찾아 실행하고, 처리가 끝나면 원래 프로세스로 돌아가거나 스케줄러를 호출한다.


🏗️ Publisher-Subscriber 패턴

📰 Publisher (발행자) 역할

Publisher는 이벤트나 메시지를 발생시키는 쪽이다. 시스템에서는 이벤트를 생성하는 하드웨어나 프로세스가 Publisher 역할을 한다.

예를 들어, 네트워크 카드가 패킷을 받으면 인터럽트를 발생시키거나, 타이머가 설정된 시간에 도달하면 타이머 인터럽트를 발생시키는 것이 Publisher의 역할이다. 신문사가 신문을 발행하는 것처럼, 어떤 일이 일어났다는 소식을 알린다.

Publisher는 누가 자신의 소식을 받는지 신경 쓰지 않는다. 그냥 소식이 있으면 알릴 뿐이다.

// Publisher 예시 - 신문사
class NewsPublisher {
  publishNews(news) {
    console.log(`뉴스 발행: ${news}`);
    // 구독자들에게 소식 전달
  }
}

👥 Subscriber (구독자) 역할

Subscriber는 Publisher의 소식을 받아보는 쪽이다. 시스템에서는 인터럽트 핸들러나 특정 이벤트를 기다리는 프로세스가 Subscriber 역할을 한다.

신문을 구독하는 사람들처럼, 관심 있는 소식이 오면 그에 맞게 반응한다. 각 Subscriber는 자신만의 방식으로 소식에 반응할 수 있다.

// Subscriber 예시 - 구독자들
class NewsReader {
  receiveNews(news) {
    console.log(`뉴스 읽기: ${news}`);
  }
}

class NewsAnalyst {
  receiveNews(news) {
    console.log(`뉴스 분석: ${news}`);
  }
}

🔄 둘 사이의 관계

Publisher와 Subscriber는 서로 직접 알 필요가 없다. 운영체제가 중간에서 이벤트 디스패처 역할을 한다.

마치 라디오 방송국과 청취자들처럼, 방송국은 그냥 전파를 보내고 라디오를 틀어놓은 사람들이 받아서 듣는다.

특징:

  • 느슨한 결합: 서로 독립적으로 동작
  • 확장성: 구독자를 쉽게 추가/제거 가능
  • 재사용성: Publisher와 Subscriber를 다른 곳에서도 사용 가능

프로세스 간 통신에서도 이 패턴이 많이 사용된다. 메시지 큐, 파이프, 공유 메모리를 통해 한 프로세스가 메시지를 발행하면 관심 있는 다른 프로세스들이 받아서 처리한다.


🎪 Event Emitter란?

Event Emitter는 이벤트를 발생시키고 관리하는 도구다. Publisher-Subscriber 패턴을 쉽게 구현할 수 있게 도와주는 일종의 중간 관리자 역할을 한다.

시스템에서는 이벤트 루프나 이벤트 디스패처가 Event Emitter와 비슷한 역할을 한다. 여러 소스에서 발생하는 이벤트들을 수집하고, 적절한 핸들러에게 전달한다.

주요 기능:

  • 이벤트 등록: “이런 이벤트가 일어나면 알려줘”
  • 이벤트 발생: “지금 이런 일이 일어났어!”
  • 이벤트 전달: 관련된 모든 사람들에게 소식 전달

Event Emitter는 우리 일상 곳곳에 있다. 예를 들어, 앱에서 “새 메시지 도착” 이벤트가 발생하면, 시스템이 이벤트를 받아서 알림을 표시하고, 사용자가 알림을 보고 반응(메시지 확인, 무시 등)한다.

이처럼 Event Emitter는 복잡한 시스템에서 각 부분들이 서로 소통할 수 있게 도와주는 핵심 도구다.


비동기 처리와 이벤트 기반 프로그래밍은 사용자와 상호작용하는 프로그램이나 여러 작업을 동시에 처리해야 하는 시스템에서는 매우 중요하다.

이런 패턴들을 잘 활용해보자! 🚀

Leave a comment