NodeJS Child_process의 spawn(), exec(), fork()

2023. 3. 5. 14:48ㆍ언어/Javascript(Node, TS...)

NodeJS λŸ°νƒ€μž„μ—μ„œ μƒˆλ‘œμš΄ ν”„λ‘œμ„ΈμŠ€λ₯Ό μƒμ„±ν•˜κ³  μ‹Άλ‹€λ©΄

일반적으둜 Child_process νŒ¨ν‚€μ§€μ˜ λ‹€μŒμ˜ λ©”μ„œλ“œλ“€μ„ ν™œμš©ν•  수 μžˆλ‹€.

  • Child_process.fork()
  • Child_process.exec()
  • Child_process.spawn()

μ—¬κΈ°μ—μ„œλŠ” μœ„ λ©”μ„œλ“œλ“€μ΄ μž‘λ™ν•˜λŠ” 방식을 동λͺ…μ˜ μ‹œμŠ€ν…œ μ½œλ“€κ³Ό μ—°κ΄€ 지어 λΉ„κ΅ν•˜κ³ μž ν•œλ‹€.

 

 

λ“€μ–΄κ°€κΈ° 전에...

이 글을 μ΄ν•΄ν•˜κΈ° μœ„ν•΄μ„œλŠ” μ‹œμŠ€ν…œ 콜과 μ‹œμŠ€ν…œ 콜의 μ’…λ₯˜μΈ fork()와 exec()이 무엇인지 μ•Œμ•„μ•Όν•œλ‹€.

이듀에 λŒ€ν•΄ λ¨Όμ € κ°„λ‹¨ν•˜κ²Œ μ†Œκ°œν•˜κ³  λ„˜μ–΄κ°€κ² λ‹€.

μ‹œμŠ€ν…œ 콜

μ‹œμŠ€ν…œ μ½œμ΄λž€ μœ μ € λͺ¨λ“œμ—μ„œ μž‘λ™ν•˜λŠ” ν”„λ‘œμ„ΈμŠ€λ“€μ΄ 운영체제의 κΆŒν•œμ΄ ν•„μš”ν•œ μž‘μ—…μ„ μš΄μ˜μ²΄μ œμ— μš”μ²­ν•˜κΈ° μœ„ν•΄ ν˜ΈμΆœλ˜λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ˜λ―Έν•œλ‹€.

파일 μž…μΆœλ ₯μ΄λ‚˜ ν”„λ‘œμ„ΈμŠ€μ˜ 생성 및 μ‹€ν–‰ λ“±μ˜ μž‘μ—…μ— λŒ€ν•œ κΆŒν•œμ΄ ν”„λ‘œμ„ΈμŠ€λ“€μ—κ²Œ μžˆμ„ 경우

μ»΄ν“¨ν„°λŠ” μ™ΈλΆ€ κ³΅κ²©μœΌλ‘œλΆ€ν„° 맀우 취약해지기 λ•Œλ¬Έμ—,

ν•΄λ‹Ή μž‘μ—…λ“€μ— λŒ€ν•œ κΆŒν•œμ€ μš΄μ˜μ²΄μ œμ—κ²Œ 있으며 ν”„λ‘œμ„ΈμŠ€λ“€μ€ μš΄μ˜μ²΄μ œμ— 이λ₯Ό μš”μ²­ν•˜λŠ” λ°©μ‹μœΌλ‘œ μž‘λ™ν•œλ‹€.

fork()

fork() λŠ” ν”„λ‘œμ„ΈμŠ€λ₯Ό μƒμ„±ν•˜λŠ” μ‹œμŠ€ν…œ 콜둜, λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€λ₯Ό λ³΅μ‚¬ν•˜λŠ” λ°©μ‹μœΌλ‘œ μž‘λ™ν•œλ‹€. 

fork() κ°€ 호좜된 μ‹œμ μ˜ μ‹€ν–‰ μ»¨ν…μŠ€νŠΈλ₯Ό μ™„μ „νžˆ λ³΅μ‚¬ν•˜κΈ° λ•Œλ¬Έμ—,

μžμ‹ ν”„λ‘œμ„ΈμŠ€λŠ” λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€ μ½”λ“œμ˜ μ‹œμž‘ 지점뢀터 μ‹€ν–‰λ˜λŠ” 것이 μ•„λ‹Œ fork() 호좜 μ΄ν›„μ˜ μ½”λ“œ 흐름이 μ‹€ν–‰λœλ‹€.

fork() 의 λ°˜ν™˜ 값은 λΆ€λͺ¨μ™€ μžμ‹μ—μ„œ μ„œλ‘œ λ‹€λ₯Έλ°, λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ—μ„œλŠ” μžμ‹ ν”„λ‘œμ„ΈμŠ€μ˜ Idκ°€ λ°˜ν™˜λ˜κ³  μžμ‹ ν”„λ‘œμ„ΈμŠ€μ—μ„œλŠ” 0이 λ°˜ν™˜λœλ‹€. 

λ”°λΌμ„œ fork() 호좜 이후 if λΆ„κΈ°λ₯Ό 톡해 λ‹€λ₯Έ μ½”λ“œ 흐름이 μ‹€ν–‰λ˜λ„λ‘ μ½”λ“œλ₯Ό ꡬ성할 수 μžˆλ‹€.

exec()

exec() λŠ” fork()μ™€λŠ” 달리 μƒˆλ‘œμš΄ ν”„λ‘œμ„ΈμŠ€λ₯Ό μƒμ„±ν•˜λŠ” μ‹œμŠ€ν…œ 콜이 μ•„λ‹ˆλ‹€.

exec() 이 호좜되면 κΈ°μ‘΄ ν”„λ‘œμ„ΈμŠ€λ₯Ό μƒˆλ‘œμš΄ ν”„λ‘œμ„ΈμŠ€λ‘œ μ™„μ „νžˆ λŒ€μ²΄ν•œλ‹€. λ”°λΌμ„œ μƒˆλ‘œμš΄ μ½”λ“œ 흐름이 μž‘λ™ν•˜κΈ°λŠ” ν•˜μ§€λ§Œ κΈ°μ‘΄ ν”„λ‘œμ„ΈμŠ€λŠ” μ™„μ „νžˆ μ‚¬λΌμ§€κ²Œ λœλ‹€.

λ¦¬λˆ…μŠ€μ—μ„œλŠ” fork()와 exec()의 쑰합을 톡해 μ‰˜μ„ κ΅¬ν˜„ν•˜κ³  μžˆλ‹€.

 

 

Spawn λ©”μ„œλ“œ

이제 본둠으둜 λŒμ•„μ™€μ„œ, Child_process νŒ¨ν‚€μ§€μ˜ ν”„λ‘œμ„ΈμŠ€ 생성 λ©”μ„œλ“œλ“€μ„ λ“€μ—¬λ‹€λ³΄μž.

NodeJS 곡식 λ¬Έμ„œμ— μž‘μ„±λœ spawn λ©”μ„œλ“œμ˜ μ‚¬μš© μ˜ˆμ‹œλŠ” λ‹€μŒκ³Ό κ°™λ‹€.

const { spawn } = require('node:child_process');
const ls = spawn('ls', ['-lh', '/usr']);

ls.stdout.on('data', (data) => {
  console.log(`stdout: ${data}`);
});

ls.stderr.on('data', (data) => {
  console.error(`stderr: ${data}`);
});

ls.on('close', (code) => {
  console.log(`child process exited with code ${code}`);
});

spawn λ©”μ„œλ“œλŠ” λͺ…령어와 μΈμžλ“€μ„ μž…λ ₯ λ°›κ³  이λ₯Ό μ΄μš©ν•˜μ—¬ μƒˆλ‘œμš΄ ν”„λ‘œμ„ΈμŠ€λ₯Ό μƒμ„±ν•œλ‹€.

ν•΄λ‹Ή λ©”μ„œλ“œλŠ” ChildProcess 객체λ₯Ό λ°˜ν™˜ν•˜κ³ , 이후 ν‘œμ€€ μž…μΆœλ ₯을 톡해 λΆ€λͺ¨μ™€ μžμ‹ κ°„ 데이터 전솑(IPC)이 κ°€λŠ₯ν•˜λ‹€.

이 λ•Œ λ°μ΄ν„°λŠ” Stream의 ν˜•νƒœλ₯Ό λ„κ²Œ λœλ‹€.

spawn λ©”μ„œλ“œκ°€ μ‹€ν–‰λ˜λ©΄ μ°¨λ‘€λŒ€λ‘œ fork(), exec() μ‹œμŠ€ν…œ 콜이 호좜되고, μΆ”κ°€μ μœΌλ‘œ IPCλ₯Ό μœ„ν•œ pipe() 도 ν˜ΈμΆœλœλ‹€.

이 λ•Œ exec으둜 λͺ…λ Ήμ–΄λ₯Ό 직접 μ‹€ν–‰ν•˜κΈ° λ•Œλ¬Έμ— μ‰˜μ€ κ΄€μ—¬ν•˜μ§€ μ•ŠλŠ”λ‹€.

 

 

Exec λ©”μ„œλ“œ

곡식 λ¬Έμ„œμ˜ exec() λ©”μ„œλ“œ μ˜ˆμ‹œλŠ” λ‹€μŒκ³Ό κ°™λ‹€. 

const { exec } = require('node:child_process');
exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  console.log(`stdout: ${stdout}`);
  console.error(`stderr: ${stderr}`);
});

exec() λ©”μ„œλ“œλŠ” exec() μ‹œμŠ€ν…œ 콜과 달리, μƒˆλ‘œμš΄ ν”„λ‘œμ„ΈμŠ€λ₯Ό μƒμ„±ν•œλ‹€.

λͺ…λ Ήμ–΄λ₯Ό ν•˜λ‚˜μ˜ λ¬Έμžμ—΄μ— λͺ¨λ‘ λ‹΄μ•„ ν˜ΈμΆœν•˜κ³ , μžμ‹κ³Ό λΆ€λͺ¨ κ°„μ˜ 톡신은 ν•¨κ»˜ μ „λ‹¬λ˜λŠ” 콜백 ν•¨μˆ˜λ₯Ό 톡해 이루어진닀.

spawn() λ©”μ„œλ“œμ™€ κΈ°λŠ₯ 상 μœ μ‚¬ν•΄ λ³΄μ΄λ‚˜ 이런 차이가 μ‘΄μž¬ν•˜λŠ” μ΄μœ κ°€ 뭘까?

spawn() vs exec()

두 λ©”μ„œλ“œμ˜ μ°¨μ΄λŠ” 크게 2가지이닀.

  • λͺ…λ Ήμ–΄ 전달 방식
  • λΆ€λͺ¨ μžμ‹ κ°„μ˜ 톡신 κ΅¬ν˜„ 방식

첫번째 차이의 μ΄μœ λŠ” λ°”λ‘œ μ‰˜μ˜ μ‹€ν–‰ μœ λ¬΄μ΄λ‹€.

spawn()은 μ‰˜μ„ μ‹€ν–‰μ‹œν‚€μ§€ μ•Šμ§€λ§Œ exec()은 μ‰˜μ„ μ‹€ν–‰μ‹œν‚¨λ‹€.

ꡬ체적으둜, exec() λ©”μ„œλ“œλŠ” λ‚΄λΆ€μ μœΌλ‘œ spawn() λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜μ—¬ μ‰˜μ„ μ‹€ν–‰μ‹œν‚€κ³  ν•΄λ‹Ή μ‰˜μ—μ„œ λͺ…λ Ήμ–΄λ₯Ό μ‹€ν–‰μ‹œν‚€λŠ” λ°©μ‹μœΌλ‘œ μž‘λ™ν•œλ‹€.

이둜 인해 exec() λ©”μ„œλ“œμ—μ„œλŠ” μ‰˜μ—μ„œ μ œκ³΅ν•˜λŠ” κΈ°λŠ₯의 이용이 κ°€λŠ₯ν•˜λ‹€. μ˜ˆμ‹œλ‘œλŠ” μ‰˜μ˜ νŒŒμ΄ν”„ κΈ°λŠ₯이 μžˆλ‹€. (λͺ…λ Ήμ–΄ μ—°κ²° κΈ°λŠ₯, pipe() μ‹œμŠ€ν…œ 콜과 μ™„μ „νžˆ 닀름)

λ”°λΌμ„œ spawn() λ©”μ„œλ“œμ—λŠ” ν•˜λ‚˜μ˜ λͺ…λ Ήμ–΄λ§Œ 전달할 수 μžˆμœΌλ‚˜, exec() λ©”μ„œλ“œλŠ” μ—¬λŸ¬ λͺ…λ Ήμ–΄λ₯Ό μ—°κ²°ν•˜μ—¬ 전달할 수 μžˆλ‹€.

λ‘λ²ˆμ§Έ 차이의 μ΄μœ λŠ” ν”„λ‘œμ„ΈμŠ€ κ°„ 데이터 톡신에 μ΄μš©λ˜λŠ” 방식에 μžˆλ‹€.

spawn() λ©”μ„œλ“œλŠ” Stream을 μ΄μš©ν•œλ‹€. 이에 μ‹€μ‹œκ°„μœΌλ‘œ 데이터λ₯Ό μˆ˜μ‹ ν•  수 μžˆμ–΄μ•Όν•˜κΈ° λ•Œλ¬Έμ— 이벀트 등둝 λ°©μ‹μœΌλ‘œ 데이터λ₯Ό μ£Όκ³  λ°›λŠ”λ‹€.

exec() λ©”μ„œλ“œλŠ” Buffered을 μ΄μš©ν•œλ‹€. 즉, μžμ‹ ν”„λ‘œμ„ΈμŠ€μ˜ 데이터λ₯Ό 차곑차곑 μŒ“μ•„ ν•œλ²ˆμ— μ „λ‹¬ν•˜κΈ° λ•Œλ¬Έμ— ν•˜λ‚˜μ˜ μ½œλ°±μ—μ„œ λͺ¨λ“  μˆ˜μ‹ μ΄ κ°€λŠ₯ν•˜λ‹€.

μ΄λŸ¬ν•œ 차이 λ•Œλ¬Έμ— λŒ€κ·œλͺ¨ λ°μ΄ν„°μ˜ μ†‘μˆ˜μ‹ μ΄ ν•„μš”ν•  λ•Œμ—λŠ” Stream을 μ΄μš©ν•˜λŠ” spawn() λ©”μ„œλ“œκ°€ μœ λ¦¬ν•˜λ‹€. 

exec() λ©”μ„œλ“œλ₯Ό ν™œμš©ν•œλ‹€λ©΄ 데이터 처리λ₯Ό μœ„ν•œ μ˜€λ²„ν—€λ“œκ°€ 컀지고, 데이터가 λ„ˆλ¬΄ μ»€μ§€λŠ” κ²½μš°μ—λŠ” μ•„μ˜ˆ μ—λŸ¬κ°€ λ°œμƒν•œλ‹€.

 

Fork λ©”μ„œλ“œ

κ³΅μ‹λ¬Έμ„œμ˜ fork() λ©”μ„œλ“œ μ˜ˆμ œλŠ” λ‹€μŒκ³Ό κ°™λ‹€.

if (process.argv[2] === 'child') {
  setTimeout(() => {
    console.log(`Hello from ${process.argv[2]}!`);
  }, 1_000);
} else {
  const { fork } = require('node:child_process');
  const controller = new AbortController();
  const { signal } = controller;
  const child = fork(__filename, ['child'], { signal });
  child.on('error', (err) => {
    // This will be called with err being an AbortError if the controller aborts
  });
  controller.abort(); // Stops the child process
}

μœ„ μ˜ˆμ‹œλŠ” 슀슀둜의 볡사본을 λ§Œλ“œλŠ” μ˜ˆμ‹œλ‘œ, μ‹œμŠ€ν…œ 콜의 fork와 μœ μ‚¬ν•˜κ²Œ λ™μž‘ν•˜μ§€λ§Œ Child process의 fork() λ©”μ„œλ“œλŠ” λ‹€λ₯Έ js νŒŒμΌμ— λŒ€ν•΄μ„œλ„ μž‘λ™ν•œλ‹€. μ˜ˆμ‹œλŠ” μ•„λž˜μ™€ κ°™λ‹€.

// parent.js
const { fork } = require('child_process');
const path = require('path');

const child = fork(path.join(__dirname, 'child.js'));

child.on('message', (msg) => {
  console.log(`Parent process received message: ${msg}`);
});

child.send('Hello from parent process!');
// child.js
process.on('message', (msg) => {
  console.log(`Child process received message: ${msg}`);
  process.send('Hello from child process!');
});

μœ„ μ˜ˆμ‹œλ₯Ό 톡해 μ•Œ 수 μžˆλŠ” 점은 μš°μ„  fork() λ©”μ„œλ“œλŠ” 였직 NodeJS (js파일)에 λŒ€ν•΄μ„œλ§Œ μž‘λ™ν•œλ‹€λŠ” 것이닀. 

NodeJS κ³΅μ‹λ¬Έμ„œμ—μ„œλ„ fork() λ©”μ„œλ“œλŠ” spawn() λ©”μ„œλ“œμ˜ μŠ€νŽ˜μ…œ μΌ€μ΄μŠ€(Node λͺ…λ Ήμ–΄ μˆ˜ν–‰)라고 μ–ΈκΈ‰ν•˜κ³  μžˆλ‹€.

The child_process.fork() method is a special case of child_process.spawn() used specifically to spawn new Node.js processes.

λ˜ν•œ μžμ‹ ν”„λ‘œμ„ΈμŠ€(child.js)μ—μ„œ νŠΉλ³„ν•œ μ„€μ • 없이 process 객체에 μ ‘κ·Όν•˜μ—¬ λΆ€λͺ¨ ν”„λ‘œμ„ΈμŠ€μ™€ 톡신을 μˆ˜ν–‰ν•˜λŠ”λ°, μ΄λŠ” fork() λ©”μ„œλ“œλ₯Ό 톡해 μ‹€ν–‰λœ ν”„λ‘œμ„ΈμŠ€λŠ” 기본적으둜 빌트인 IPC ꡬ성을 ν¬ν•¨ν•˜κ³  있기 λ•Œλ¬Έμ΄λ‹€. 

λ”°λΌμ„œ fork() λ©”μ„œλ“œλ₯Ό ν™œμš©ν•˜λ©΄ Node ν”„λ‘œμ„ΈμŠ€ 간에 Stream 톡신을 μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆλ‹€.

 

μ°Έμ‘°

https://nodejs.org/api/child_process.html#child-process

https://stackoverflow.com/questions/48698234/node-js-spawn-vs-execute

https://stackoverflow.com/questions/17861362/node-js-child-process-difference-between-spawn-fork

 

Child process | Node.js v19.7.0 Documentation

Child process# Source Code: lib/child_process.js The node:child_process module provides the ability to spawn subprocesses in a manner that is similar, but not identical, to popen(3). This capability is primarily provided by the child_process.spawn() functi

nodejs.org

 

Node.js Spawn vs. Execute

In an online training video I am watching to learn Node, the narrator says that "spawn is better for longer processes involving large amounts of data, whereas execute is better for short bits of da...

stackoverflow.com

 

node.js child process - difference between spawn & fork

This might seem like a basic question, but I could not find any documentation : What is the difference between forking & spawning a node.js process? I have read that forking is a special case of

stackoverflow.com

'μ–Έμ–΄ > Javascript(Node, TS...)' μΉ΄ν…Œκ³ λ¦¬μ˜ λ‹€λ₯Έ κΈ€

Interface vs Abstract Class (in Typescript)  (0) 2023.03.19
Interface in TS (vs Java)  (0) 2023.03.19
TypeScript νƒ€μž…: any, unknown, never  (0) 2023.03.09