From b3aecb2a8227460a04aebd9cc8c57db8a24e03cb Mon Sep 17 00:00:00 2001 From: eeeasycode Date: Mon, 8 Dec 2025 00:32:38 +0900 Subject: [PATCH] =?UTF-8?q?week7=20-=20chapter11=20=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EC=83=9D=EA=B0=81=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chapter11/changmin/README.md | 202 ++++++++++++++++++ .../changmin/deep_dive/canceling_async_ops.md | 180 ++++++++++++++++ .../changmin/deep_dive/cpu_bound_tasks.md | 41 ++++ .../experiments/image-processing/blocking.cjs | 30 +++ .../experiments/image-processing/blocking.js | 30 +++ .../image-processing/non-blocking.cjs | 41 ++++ .../image-processing/non-blocking.js | 41 ++++ .../experiments/image-processing/task.cjs | 31 +++ .../experiments/image-processing/task.js | 29 +++ 9 files changed, 625 insertions(+) create mode 100644 chapter11/changmin/README.md create mode 100644 chapter11/changmin/deep_dive/canceling_async_ops.md create mode 100644 chapter11/changmin/deep_dive/cpu_bound_tasks.md create mode 100644 chapter11/changmin/experiments/image-processing/blocking.cjs create mode 100644 chapter11/changmin/experiments/image-processing/blocking.js create mode 100644 chapter11/changmin/experiments/image-processing/non-blocking.cjs create mode 100644 chapter11/changmin/experiments/image-processing/non-blocking.js create mode 100644 chapter11/changmin/experiments/image-processing/task.cjs create mode 100644 chapter11/changmin/experiments/image-processing/task.js diff --git a/chapter11/changmin/README.md b/chapter11/changmin/README.md new file mode 100644 index 0000000..1a57acb --- /dev/null +++ b/chapter11/changmin/README.md @@ -0,0 +1,202 @@ +# Node.js Design Patterns + +## Chapter 11 - Advanced Recipes (고급 레시피) + +Node.js 개발 시 흔히 마주치는 까다로운 문제들을 해결하기 위한 4가지 고급 패턴(레시피)을 다루는 챕터 + +### 핵심 내용 + +비동기 프로그래밍의 복잡성을 관리하고, CPU 집약적인 작업을 효율적으로 처리하는 방법에 초점 + +- **Asynchronously Initialized Components**: 초기화가 완료되지 않은 컴포넌트를 안전하게 사용하는 방법 +- **Asynchronous Request Batching and Caching**: 중복 요청을 방지하고 결과를 재사용하여 고부하 상황에서 성능 최적화 +- **Canceling Asynchronous Operations**: 불필요해진 긴 비동기 작업을 중단하여 리소스 낭비 방지 +- **Running CPU-bound Tasks**: 이벤트 루프를 차단하지 않고 CPU 집약적인 작업을 처리하는 방법 + +--- + +## 1. Dealing with Asynchronously Initialized Components + +### 1.1 문제 정의 + +데이터베이스 드라이버나 미들웨어 클라이언트처럼 **초기화에 네트워크 통신이 필요하여 즉시 사용할 수 없는 컴포넌트**를 다룰 때 발생하는 문제 + +**문제점:** 컴포넌트가 완전히 초기화되기 전에 클라이언트가 API를 호출하면 오류가 발생하거나 예상치 못한 동작을 할 수 있음 + +### 1.2 해결책 + +#### 1.2.1 Local Initialization Check (로컬 초기화 확인) + +API를 호출할 때마다 초기화 여부를 확인하고, 초기화되지 않았다면 오류를 반환하거나 기다리는 방식 + +- **단점**: 모든 메서드에 체크 로직이 들어가야 하므로 **코드가 복잡해지고 중복이 발생**함 (Business Logic과 Initialization Logic의 혼재) + +#### 1.2.2 Delayed Startup (지연된 시작) + +모든 비동기 서비스가 초기화될 때까지 애플리케이션의 시작 자체를 미루는 방식 + +- **특징**: 가장 단순하지만, 초기화가 오래 걸리면 서버 시작이 늦어짐 + +#### 1.2.3 Pre-initialization Queues (초기화 전 큐) - **권장** + +컴포넌트가 초기화되기 전에 들어온 요청을 **큐(Queue)**에 쌓아두었다가, 초기화가 완료되면 큐에 쌓인 명령들을 순차적으로 실행하는 방식 + +- **구현 방법**: **State 패턴**을 사용하여 '초기화 중(Queuing State)'과 '초기화 완료(Ready State)'를 분리하면 깔끔하게 구현 가능 +- **장점**: 클라이언트는 초기화 여부를 신경 쓰지 않고 메서드를 호출할 수 있음 (Transparent) + +```javascript +class Database { + constructor() { + this.state = new QueuingState(this); + this.init(); + } + + init() { + connectToServer().then(() => { + this.state = new ReadyState(this); // 상태 전환 + this.state.flushQueue(); // 큐에 쌓인 작업 실행 + }); + } + + query(sql) { + return this.state.query(sql); // 현재 상태에 따라 큐잉하거나 바로 실행 + } +} +``` + +**State 패턴을 활용하여 초기화 복잡성을 내부로 캡슐화하는 것이 핵심** + +--- + +## 2. Asynchronous Request Batching and Caching + +### 2.1 문제 정의 + +고부하 애플리케이션에서 동일한 API에 대한 중복 호출이 많을 때, 불필요한 리소스 소모와 성능 저하 발생 + +### 2.2 해결책 + +#### 2.2.1 Asynchronous Request Batching (비동기 요청 배칭) + +동일한 API에 대해 여러 요청이 동시에 들어올 경우, 새로운 요청을 생성하지 않고 **이미 진행 중인 요청(Promise)에 편승(piggyback)**하여 결과를 공유하는 방식 + +- **목적**: 중복 연산 및 I/O 호출 최소화 + +#### 2.2.2 Asynchronous Request Caching (비동기 요청 캐싱) + +요청이 완료된 후에도 결과(Promise)를 일정 시간 동안 저장하여 재사용하는 방식 + +- **목적**: 응답 속도 향상 및 백엔드 부하 감소 + +#### 2.2.3 결합된 패턴 (Batching + Caching) + +배칭과 캐싱을 결합하여 최적의 성능 달성. 진행 중인 요청은 배칭으로 처리하고, 완료된 요청은 캐싱된 값으로 처리 + +**주의사항: Zalgo 방지** +캐시된 값을 반환할 때도 반드시 **비동기적으로 반환**해야 함. (동기/비동기가 섞이면 예기치 않은 버그 발생) + +```javascript +let cache = new Map(); + +function getStockPrices(item) { + if (cache.has(item)) { + return Promise.resolve(cache.get(item)); // 항상 비동기 반환 + } + + const promise = fetchPrices(item).then((prices) => { + cache.set(item, prices); + return prices; + }); + + cache.set(item, promise); // Promise 자체를 캐싱하여 배칭 효과 + return promise; +} +``` + +**Promise를 캐싱함으로써 배칭과 캐싱을 동시에 해결하는 것이 우아한 패턴** + +--- + +## 3. Canceling Asynchronous Operations + +### 3.1 문제 정의 + +사용자가 작업을 취소하거나(예: 파일 업로드 취소), 타임아웃 등으로 인해 더 이상 결과가 필요 없을 때, 진행 중인 긴 비동기 작업을 중단해야 함 + +### 3.2 해결책 + +#### 3.2.1 기본 원칙 + +비동기 단계 사이마다 취소 여부를 확인하고, 취소가 요청되었다면 `CancelError`와 같은 예외를 던져 작업을 중단 + +#### 3.2.2 Wrapper 함수 + +비동기 함수 호출을 감싸서 취소 로직을 자동화하는 래퍼(Wrapper)를 생성하여 사용 + +#### 3.2.3 Generators (Cancelable Async Flows) + +제너레이터를 사용하여 `async/await`와 유사한 흐름을 유지하면서도, 외부에서 제너레이터의 흐름을 제어(취소)할 수 있는 방식 + +- **장점**: 코드 가독성을 `async/await` 수준으로 유지하면서도, Promise에는 없는 "외부에서의 제어권"을 가질 수 있음 + +```javascript +function* uploadTask() { + try { + yield uploadChunk(1); + yield uploadChunk(2); // 도중에 취소되면 여기서 멈춤 + } catch (err) { + if (err instanceof CancelError) console.log("Upload canceled"); + } +} +``` + +**Promise 자체는 취소 API가 표준화되어 있지 않으므로, Generator나 AbortController 등을 활용해야 함** + +--- + +## 4. Running CPU-bound Tasks + +### 4.1 문제 정의 + +Node.js는 **단일 스레드(Single Thread)** 기반이므로, CPU를 많이 사용하는 작업(암호화, 압축, 복잡한 계산)을 메인 스레드에서 실행하면 **이벤트 루프가 차단(Block)**되어 서버가 멈춤 + +### 4.2 해결책 + +#### 4.2.1 Interleaving with setImmediate (인터리빙) + +긴 작업을 작은 단계로 쪼개고, 각 단계 사이에 `setImmediate()`를 사용하여 제어권을 이벤트 루프로 잠시 넘겨줌 + +- **효과**: 긴 작업 중에도 I/O 처리가 중간중간 수행될 수 있어 서버의 응답성(Responsiveness) 유지 + +#### 4.2.2 Child Processes (외부 프로세스) + +`child_process.fork()`를 사용하여 별도의 프로세스에서 작업을 실행 + +- **장점**: 메인 프로세스와 완전히 분리됨 +- **단점**: 프로세스 생성 비용이 높고 메모리를 많이 차지함. **프로세스 풀(Process Pool)** 사용 권장 + +#### 4.2.3 Worker Threads (워커 스레드) - **최신/권장** + +`worker_threads` 모듈을 사용하여 스레드 기반으로 병렬 처리 + +- **장점**: 프로세스보다 가볍고, `SharedArrayBuffer`를 통해 메모리를 공유할 수 있어 데이터 전송 비용이 낮음 +- **결론**: **CPU 집약적 작업에 가장 적합한 Node.js의 표준 방법** + +--- + +## 결론 + +이 챕터는 Node.js의 비동기 특성을 유지하면서 현실적인 문제들을 해결하는 패턴을 제시함 + +| 패턴 | 문제 상황 | 해결 전략 | +| :----------------------- | :------------------------- | :----------------------------------------------- | +| **Async Initialization** | 초기화 덜 된 컴포넌트 사용 | **State 패턴 + Queue**로 요청 대기 | +| **Batching & Caching** | 고부하 시 중복 요청 폭주 | **Promise 캐싱**으로 요청 병합 | +| **Cancel Async Ops** | 불필요한 작업 리소스 낭비 | **Generator** 또는 AbortController로 중단점 생성 | +| **CPU-bound Tasks** | 이벤트 루프 차단 (렉 걸림) | **Worker Threads**로 작업 위임 | + +**NestJS 기반 백엔드 개발 시 고려사항:** + +- **초기화:** `OnModuleInit` 등을 활용하되, 외부 의존성이 강한 경우 큐잉 패턴 고려 +- **캐싱:** Interceptor 등을 활용한 요청 캐싱 및 배칭 적용 +- **CPU 작업:** 이미지 처리, 엑셀 파싱 등은 반드시 Worker Thread나 별도 Microservice로 분리하여 메인 스레드 보호 diff --git a/chapter11/changmin/deep_dive/canceling_async_ops.md b/chapter11/changmin/deep_dive/canceling_async_ops.md new file mode 100644 index 0000000..e520474 --- /dev/null +++ b/chapter11/changmin/deep_dive/canceling_async_ops.md @@ -0,0 +1,180 @@ +# Deep Dive: Canceling Asynchronous Operations + +### 핵심 내용 + +Node.js의 비동기 작업은 기본적으로 **"실행하면 끝까지 간다(Fire-and-Forget)"**는 특성이 있음. +중간에 취소하기 위해서는 **"외부에서 제어 가능한 흐름"**을 만들어야 함. + +- **Promise의 한계:** 표준 Promise API에는 `cancel()`이 없음 +- **Generator 활용:** `yield`를 통해 실행 제어권을 호출자에게 위임하여 취소 가능하게 함 +- **AbortController:** 최신 표준 API로, `signal`을 통해 취소 이벤트를 전파함 + +--- + +## 1. The Problem: Promise는 멈추지 않는다 + +일반적인 비동기 함수는 한 번 호출되면 멈출 수 없음. + +```javascript +async function upload() { + await step1(); + // <-- 여기서 사용자가 "취소" 버튼을 눌러도 + await step2(); // <-- 이 코드는 실행됨 (자원 낭비) + await step3(); +} +``` + +### 왜 문제인가? +1. **리소스 낭비:** 결과가 필요 없는데도 CPU와 네트워크를 계속 사용함 +2. **부작용(Side Effect):** 이미 페이지를 벗어났는데 뒤늦게 UI 업데이트를 시도하다 에러 발생 (React의 "Can't perform a React state update on an unmounted component" 경고) + +--- + +## 2. Generator를 활용한 제어권 역전 (Inversion of Control) + +Generator 함수(`function*`)는 `yield`를 만날 때마다 멈추고 제어권을 외부(Caller)로 넘김. +이를 이용하면 **"다음 단계로 넘어갈지, 아니면 멈출지"**를 외부에서 결정할 수 있음. + +### 2.1 기본 구조 + +```javascript +function* task() { + console.log('Step 1'); + yield 1; + + console.log('Step 2'); + yield 2; + + console.log('Step 3'); +} + +const iterator = task(); +iterator.next(); // Step 1 +// 여기서 멈춤. iterator.next()를 안 부르면 Step 2는 영원히 실행 안 됨. +``` + +### 2.2 Cancelable Async Runner 구현 + +Promise와 Generator를 결합하여 **"취소 가능한 async/await"**를 구현하는 패턴. + +```javascript +class CancelError extends Error { + constructor() { super('Canceled'); this.isCanceled = true; } +} + +function createAsyncRunner(generatorFn) { + return function (...args) { + const generator = generatorFn(...args); + let cancelRequested = false; + + // 1. 실행을 담당하는 Promise 반환 + const promise = new Promise((resolve, reject) => { + + // 재귀적으로 next()를 호출하는 함수 + function handleNext(result) { + if (cancelRequested) { + return reject(new CancelError()); + } + + if (result.done) { + return resolve(result.value); + } + + // yield된 Promise가 완료되면 다시 handleNext 호출 + Promise.resolve(result.value) + .then(res => handleNext(generator.next(res))) + .catch(err => { + if (cancelRequested) reject(new CancelError()); + else handleNext(generator.throw(err)); + }); + } + + handleNext(generator.next()); + }); + + // 2. 취소 메서드 부착 (핵심) + promise.cancel = () => { + cancelRequested = true; + }; + + return promise; + }; +} +``` + +### 2.3 사용 예시 + +```javascript +const cancelableUpload = createAsyncRunner(function* (files) { + console.log('Start Uploading...'); + + for (const file of files) { + // 여기서 yield가 중단점 역할 + yield uploadFile(file); + console.log(`${file} uploaded`); + } + + return 'All Done'; +}); + +// 실행 +const work = cancelableUpload(['a.png', 'b.png']); + +// 도중에 취소! +setTimeout(() => { + work.cancel(); + console.log('Cancel Requested!'); +}, 100); + +work.catch(err => { + if (err instanceof CancelError) console.log('Work Canceled Cleanly'); + else console.error(err); +}); +``` + +--- + +## 3. Modern Alternative: AbortController + +Generator 패턴은 학습용으로는 훌륭하지만(원리 이해), 최신 Node.js/Web 환경에서는 **AbortController**가 표준. + +### 3.1 AbortController란? +Fetch API와 함께 도입된 취소 시그널 표준. + +```javascript +const controller = new AbortController(); +const signal = controller.signal; + +// 1. 비동기 작업에 signal 전달 +doHeavyWork({ signal }).then(() => console.log('Done')).catch(console.error); + +// 2. 취소 호출 +controller.abort(); + +// --- 구현부 --- +async function doHeavyWork({ signal }) { + if (signal.aborted) throw new Error('Aborted'); + + await step1(); + + // 중간중간 체크 or 리스너 등록 + if (signal.aborted) throw new Error('Aborted'); + + // Node.js 스트림이나 fetch에는 signal을 그대로 전달 가능 + await fetch('https://example.com', { signal }); +} +``` + +--- + +## 결론 + +**언제 무엇을 써야 할까?** + +1. **라이브러리/프레임워크 개발자라면:** Generator 기반의 Custom Runner가 제어권 확보에 유리할 수 있음 (Redux-Saga가 이 방식 사용) +2. **일반 애플리케이션 개발자라면:** 표준인 **AbortController** 사용 권장. + +**NestJS 적용 포인트** + +* HTTP 요청 취소 시 (`req.on('close')`), `AbortSignal`을 서비스 계층까지 전파하여 DB 쿼리나 외부 API 호출도 같이 취소되게 설계해야 진정한 리소스 절약이 가능함. + diff --git a/chapter11/changmin/deep_dive/cpu_bound_tasks.md b/chapter11/changmin/deep_dive/cpu_bound_tasks.md new file mode 100644 index 0000000..9176224 --- /dev/null +++ b/chapter11/changmin/deep_dive/cpu_bound_tasks.md @@ -0,0 +1,41 @@ +--- + +## Appendix: 실제 성능 비교 실험 + +단일 Node.js 메인 스레드와 Worker Threads를 사용했을 때의 성능 차이를 확인하기 위해 간단한 실험을 수행함. + +### 실험 환경 + +- **Task:** 4K 이미지(3840x2160) 버퍼를 생성하고 픽셀 반전 연산을 30회 반복 (CPU Intensive) +- **Scenario:** 4개의 작업을 동시에 요청 +- **Metric:** 총 소요 시간 및 이벤트 루프 응답성(Heartbeat) + +### 1. Blocking Mode (Main Thread) + +```javascript +// 메인 스레드에서 순차 실행 +tasks.forEach(() => processImage()); +``` + +**결과:** + +- **Total Time:** 3.384s +- **Event Loop:** **Dead (응답 없음)** +- **특징:** 작업이 진행되는 3.3초 동안 `setInterval`로 찍는 로그가 단 하나도 출력되지 않음. 서버가 완전히 멈춤. + +### 2. Non-Blocking Mode (Worker Threads) + +```javascript +// 워커 스레드로 위임 +tasks.forEach(() => new Worker("./task.js")); +``` + +**결과:** + +- **Total Time:** 0.895s (**약 3.8배 속도 향상**) +- **Event Loop:** **Alive (정상 작동)** +- **특징:** 작업 수행 중에도 `[Event Loop] Alive...` 로그가 0.1초마다 꾸준히 출력됨. + +### 결론 + +Worker Threads를 사용하면 다중 코어를 활용하여 **처리 속도(Throughput)**를 획기적으로 높일 뿐만 아니라, 메인 스레드의 **응답성(Responsiveness)**을 유지하여 다른 클라이언트의 요청을 방해하지 않음. diff --git a/chapter11/changmin/experiments/image-processing/blocking.cjs b/chapter11/changmin/experiments/image-processing/blocking.cjs new file mode 100644 index 0000000..d693f24 --- /dev/null +++ b/chapter11/changmin/experiments/image-processing/blocking.cjs @@ -0,0 +1,30 @@ +const processImage = require("./task.cjs"); + +console.log("--- [Blocking Mode] Start ---"); + +// 이벤트 루프 상태 모니터링 +let ticks = 0; +const monitor = setInterval(() => { + console.log(`[Event Loop] Alive... (tick: ${++ticks})`); +}, 100); + +const startTime = Date.now(); + +// 4개의 작업을 "동시에" 요청한다고 가정 (하지만 메인 스레드라 순차 실행됨) +const tasks = [1, 2, 3, 4]; + +console.log(`[Main] Starting ${tasks.length} tasks...`); + +// 동기적 실행 (Blocking) +tasks.forEach((id) => { + console.log(`[Task ${id}] Started`); + processImage(); // 여기서 메인 스레드가 멈춤! + console.log(`[Task ${id}] Finished`); +}); + +const endTime = Date.now(); +console.log( + `--- [Blocking Mode] All Done in ${(endTime - startTime) / 1000}s ---` +); + +clearInterval(monitor); diff --git a/chapter11/changmin/experiments/image-processing/blocking.js b/chapter11/changmin/experiments/image-processing/blocking.js new file mode 100644 index 0000000..5a76c3e --- /dev/null +++ b/chapter11/changmin/experiments/image-processing/blocking.js @@ -0,0 +1,30 @@ +const processImage = require("./task"); + +console.log("--- [Blocking Mode] Start ---"); + +// 이벤트 루프 상태 모니터링 +let ticks = 0; +const monitor = setInterval(() => { + console.log(`[Event Loop] Alive... (tick: ${++ticks})`); +}, 100); + +const startTime = Date.now(); + +// 4개의 작업을 "동시에" 요청한다고 가정 (하지만 메인 스레드라 순차 실행됨) +const tasks = [1, 2, 3, 4]; + +console.log(`[Main] Starting ${tasks.length} tasks...`); + +// 동기적 실행 (Blocking) +tasks.forEach((id) => { + console.log(`[Task ${id}] Started`); + processImage(); // 여기서 메인 스레드가 멈춤! + console.log(`[Task ${id}] Finished`); +}); + +const endTime = Date.now(); +console.log( + `--- [Blocking Mode] All Done in ${(endTime - startTime) / 1000}s ---` +); + +clearInterval(monitor); diff --git a/chapter11/changmin/experiments/image-processing/non-blocking.cjs b/chapter11/changmin/experiments/image-processing/non-blocking.cjs new file mode 100644 index 0000000..69205a2 --- /dev/null +++ b/chapter11/changmin/experiments/image-processing/non-blocking.cjs @@ -0,0 +1,41 @@ +const { Worker } = require("worker_threads"); +const path = require("path"); + +console.log("--- [Non-Blocking Mode] Start ---"); + +// 이벤트 루프 상태 모니터링 +let ticks = 0; +const monitor = setInterval(() => { + console.log(`[Event Loop] Alive... (tick: ${++ticks})`); +}, 100); + +const startTime = Date.now(); + +// 4개의 작업을 워커 스레드로 병렬 실행 +const tasks = [1, 2, 3, 4]; +let completed = 0; + +console.log(`[Main] Starting ${tasks.length} tasks...`); + +tasks.forEach((id) => { + console.log(`[Task ${id}] Scheduling...`); + + const worker = new Worker(path.join(__dirname, "task.cjs")); + + worker.on("message", (msg) => { + console.log(`[Task ${id}] Finished`); + completed++; + + if (completed === tasks.length) { + const endTime = Date.now(); + console.log( + `--- [Non-Blocking Mode] All Done in ${ + (endTime - startTime) / 1000 + }s ---` + ); + clearInterval(monitor); + } + }); + + worker.on("error", console.error); +}); diff --git a/chapter11/changmin/experiments/image-processing/non-blocking.js b/chapter11/changmin/experiments/image-processing/non-blocking.js new file mode 100644 index 0000000..8996d07 --- /dev/null +++ b/chapter11/changmin/experiments/image-processing/non-blocking.js @@ -0,0 +1,41 @@ +const { Worker } = require("worker_threads"); +const path = require("path"); + +console.log("--- [Non-Blocking Mode] Start ---"); + +// 이벤트 루프 상태 모니터링 +let ticks = 0; +const monitor = setInterval(() => { + console.log(`[Event Loop] Alive... (tick: ${++ticks})`); +}, 100); + +const startTime = Date.now(); + +// 4개의 작업을 워커 스레드로 병렬 실행 +const tasks = [1, 2, 3, 4]; +let completed = 0; + +console.log(`[Main] Starting ${tasks.length} tasks...`); + +tasks.forEach((id) => { + console.log(`[Task ${id}] Scheduling...`); + + const worker = new Worker(path.join(__dirname, "task.js")); + + worker.on("message", (msg) => { + console.log(`[Task ${id}] Finished`); + completed++; + + if (completed === tasks.length) { + const endTime = Date.now(); + console.log( + `--- [Non-Blocking Mode] All Done in ${ + (endTime - startTime) / 1000 + }s ---` + ); + clearInterval(monitor); + } + }); + + worker.on("error", console.error); +}); diff --git a/chapter11/changmin/experiments/image-processing/task.cjs b/chapter11/changmin/experiments/image-processing/task.cjs new file mode 100644 index 0000000..102f9bf --- /dev/null +++ b/chapter11/changmin/experiments/image-processing/task.cjs @@ -0,0 +1,31 @@ +const { parentPort, workerData } = require("worker_threads"); + +// 시뮬레이션할 이미지 크기 (4K 해상도 정도) +const WIDTH = 3840; +const HEIGHT = 2160; + +function processImage() { + // 가상의 이미지 데이터 생성 (약 33MB) + const buffer = Buffer.alloc(WIDTH * HEIGHT * 4); + + // 픽셀 조작 (CPU Intensive) + // 부하를 늘리기 위해 여러 번 반복 수행 (예: 복잡한 필터링 시뮬레이션) + const ITERATIONS = 30; // 30배 부하 증가 + + for (let k = 0; k < ITERATIONS; k++) { + for (let i = 0; i < buffer.length; i++) { + buffer[i] = 255 - buffer[i]; + } + } + + return `Processed ${WIDTH}x${HEIGHT} image`; +} + +// 워커 스레드로 실행될 때 +if (parentPort) { + const result = processImage(); + parentPort.postMessage(result); +} + +// 메인 스레드에서 직접 호출할 때 (블로킹 테스트용) +module.exports = processImage; diff --git a/chapter11/changmin/experiments/image-processing/task.js b/chapter11/changmin/experiments/image-processing/task.js new file mode 100644 index 0000000..9e554d0 --- /dev/null +++ b/chapter11/changmin/experiments/image-processing/task.js @@ -0,0 +1,29 @@ +const { parentPort, workerData } = require("worker_threads"); + +// 시뮬레이션할 이미지 크기 (4K 해상도 정도) +const WIDTH = 3840; +const HEIGHT = 2160; + +function processImage() { + // 가상의 이미지 데이터 생성 (약 33MB) + const buffer = Buffer.alloc(WIDTH * HEIGHT * 4); + + // 픽셀 조작 (CPU Intensive) + // 단순한 반전(Invert) 작업이지만 픽셀 수가 많아 부하가 걸림 + for (let i = 0; i < buffer.length; i++) { + buffer[i] = 255 - buffer[i]; + // 추가 부하를 위해 약간의 수학 연산 추가 + Math.sqrt(i); + } + + return `Processed ${WIDTH}x${HEIGHT} image`; +} + +// 워커 스레드로 실행될 때 +if (parentPort) { + const result = processImage(); + parentPort.postMessage(result); +} + +// 메인 스레드에서 직접 호출할 때 (블로킹 테스트용) +module.exports = processImage;