본문 바로가기
카테고리 없음

[JavaScript] Observer 설계 패턴, Worker Thread, 이벤트 루프

by hbIncoding 2024. 7. 25.

1. Observer 설계 패턴

  • 객체의 상태 변화에 따라 다른 객체들에게 자동으로 알림을 보내는 디자인 패턴
  • 주로 이벤트 핸들링 시스템에서 사용
  • 주체 객체와 업저버 객체 간의 일대 다 관계를 설정하여 상태 변화에 대해 옵저버들이 자동으로 업데이트 될 수 있도록 한다.
  • Subject (주체)
    • 상태를 가지고 있으며, 옵저버들을 관리하는 역할을 합니다.
    • 상태가 변경되면 옵저버들에게 알림을 보냅니다.
    • 옵저버들을 등록하고 제거하는 메서드를 제공합니다.
  • Observer (옵저버)
    • 주체의 상태 변화를 감지하고, 이에 반응하는 객체입니다.
    • 주체에 의해 상태 변화에 대한 알림을 받습니다.
    • 주체의 상태 변화에 따라 자신의 상태를 업데이트합니다.
  • 장점
    • 주체와 옵저버 간의 느슨한 결합(Loose Coupling)을 유지할 수 있습니다.
    • 주체의 상태 변화를 효율적으로 관리할 수 있습니다.
    • 옵저버들이 독립적으로 관리되어, 다른 옵저버들의 상태에 영향을 받지 않습니다.
  • 단점
    • 옵저버가 많아질수록 성능이 저하될 수 있습니다.
    • 주체와 옵저버 간의 관계가 복잡해질 수 있습니다.

 

2. Worker Thread

  • Node.js에서 멀티 스레드 프로그래밍을 가능하게 하는 모듈
  • 주요개념
    • Worker: 독립된 JavaScript 실행 환경을 가지며, 메인 스레드와는 별도로 실행됩니다. 새로운 Worker는 worker_threads.Worker 클래스를 사용하여 생성됩니다.
    • Main Thread: Node.js 애플리케이션이 실행되는 기본 스레드입니다. 메인 스레드는 Worker와 메시지를 주고받을 수 있습니다.
    • 메시지 전달: 메인 스레드와 Worker는 메시지 이벤트를 통해 데이터를 주고받을 수 있습니다. postMessage 메서드로 메시지를 보내고, on('message') 이벤트로 메시지를 수신합니다.
  • 장점
    • 멀티 스레드: CPU 집약적인 작업을 별도의 스레드에서 처리하여 메인 스레드의 부하를 줄일 수 있습니다.
    • 비동기 처리: 메인 스레드와 Worker가 비동기적으로 통신하여 높은 응답성을 유지할 수 있습니다.
  •  단점
    • 복잡성 증가: 멀티 스레드 프로그래밍은 단일 스레드 프로그래밍보다 복잡합니다.
    • 데이터 공유: Worker 간의 데이터 공유는 직렬화/역직렬화 과정을 거쳐야 하므로, 많은 양의 데이터를 주고받을 때 성능에 영향을 미칠 수 있습니다.
  • 사용 시기
    • CPU 집약적인 작업: 이미지 처리, 데이터 압축/해제, 대규모 계산 작업 등 CPU를 많이 사용하는 작업을 처리할 때 유용합니다.
    • I/O 집약적인 작업: Node.js는 기본적으로 비동기 I/O를 지원하므로, Worker Thread는 주로 CPU 집약적인 작업에 사용됩니다.
  • 사용 예시
main.js

const { Worker } = require('worker_threads');

const worker = new Worker('./worker.js');

worker.on('message', message => {
    console.log(`Received message from worker: ${message}`);
});

worker.on('error', error => {
    console.error(`Worker error: ${error}`);
});

worker.on('exit', code => {
    console.log(`Worker exited with code: ${code}`);
});

worker.postMessage('Hello, Worker!');

 

worker.js

const { parentPort } = require('worker_threads');

parentPort.on('message', message => {
    console.log(`Received message from main thread: ${message}`);
    parentPort.postMessage(`Hello, Main Thread!`);
});

 

3. Node 이벤트 루프 동작 방식

  • Node.js의 이벤트 루프는 비동기 I/O 작업을 효율적으로 처리하기 위한 핵심 메커니즘
  • 주로 I/O 작업을 비동기적으로 처리, 콜백 함수 관리
  • 이벤트 루프를 통해 여러 작업을 동시에 처리하는 것처럼 보이게 한다.
  • 이벤트 루프의 단계
    1. Timers 단계
      • setTimeout과 setInterval 콜백이 실행됩니다.
      • 지정된 시간이 경과한 타이머 콜백이 실행됩니다.
    2. Pending Callbacks 단계
      • I/O 콜백이 처리됩니다. 예를 들어, TCP 에러 타입의 콜백입니다.
    3. Idle, Prepare 단계
      • 내부적으로 Node.js가 사용하는 단계로, 거의 신경 쓸 필요가 없습니다.
    4. Poll 단계
      • 새로운 I/O 이벤트를 가져오고, I/O 관련 콜백이 실행됩니다.
      • 이 단계에서 이벤트 루프는 새로운 I/O 이벤트를 처리하고, 블록할 수도 있습니다.
    5. Check 단계
      • setImmediate 콜백이 실행됩니다.
      • setImmediate로 등록된 콜백이 이 단계에서 실행됩니다.
    6. Close Callbacks 단계
      • close 이벤트가 발생한 콜백이 실행됩니다. 예를 들어, socket.on('close', ...)와 같은 콜백이 실행됩니다.
  • 이벤트 루프 동작 방식
    1. Initial Phase (초기화)
      • Node.js 애플리케이션이 시작되면, 초기화 단계에서 이벤트 루프가 설정됩니다.
      • 모든 초기화 작업이 완료되면, 이벤트 루프가 시작됩니다.
    2. Execution Phase (실행)
      • 이벤트 루프는 각 단계에서 해당하는 작업을 처리합니다.
      • 단계가 끝나면 다음 단계로 이동합니다.
    3. Completion Phase (완료)
      • 모든 작업이 완료되면, 이벤트 루프는 다시 Timers 단계로 돌아갑니다.
      • 새로운 작업이 없을 때까지 반복됩니다.

4. Event Emitter 동작 방식

  • 이벤트 기반 아키텍처를 구현하는데 사용되는 핵심 모듈
  • EventEmitter를 통해 객체는 이벤트를 방출하고, 이벤트에 대한 리스너를 등록하거나 제거할 수 있다
  • 비동기 작업을 처리하고, 모듈 간의 통신을 쉽게 할 수 있도록 도와준다
  • EventEmitter의 주요 메서드와 그 동작 방식
    1. on(eventName, listener)
      • 특정 이벤트에 대한 리스너를 등록합니다.
      • eventName은 문자열로 이벤트의 이름을 나타내며, listener는 이벤트가 발생했을 때 호출될 함수입니다.
    2. emit(eventName, [...args])
      • 특정 이벤트를 방출합니다.
      • 등록된 리스너들이 호출되며, 추가적인 인자는 리스너 함수로 전달됩니다.
    3. once(eventName, listener)
      • 특정 이벤트에 대해 한 번만 호출될 리스너를 등록합니다.
      • 이벤트가 방출된 후 리스너가 자동으로 제거됩니다.
    4. removeListener(eventName, listener)
      • 특정 이벤트에 대한 리스너를 제거합니다.
      • listener는 등록된 리스너 함수와 동일한 함수여야 합니다.
    5. removeAllListeners([eventName])
      • 특정 이벤트 또는 모든 이벤트에 대한 모든 리스너를 제거합니다.
    6. listeners(eventName)
      • 특정 이벤트에 등록된 리스너 배열을 반환합니다.

5. Pub-Sub 설계 패턴

  • 메시지 발송자(publisher)와 수신자(subscriber)가 직접적으로 통신하지 않고, 발송자가 특정 채널(topic 또는 event)을 통해 메시지를 발송하면, 해당 채널을 구독(subscribe)한 수신자가 메시지를 받는 구조를 가지고 있습니다.
  • 이는 느슨한 결합(loose coupling)을 유지하면서 시스템 구성 요소 간의 통신을 가능하게 합니다.
  • Pub-Sub 패턴의 주요 구성 요소
    1. Publisher: 이벤트나 메시지를 발행하는 객체입니다. 발행자는 수신자를 알 필요가 없습니다.
    2. Subscriber: 이벤트나 메시지를 구독하는 객체입니다. 구독자는 특정 주제를 구독하며, 해당 주제의 메시지가 발행되면 알림을 받습니다.
    3. Broker: 발행자와 구독자 간의 중간 매개체 역할을 하며, 발행자가 발행한 메시지를 적절한 구독자에게 전달합니다. 브로커는 메시지의 라우팅을 담당합니다.