-
Notifications
You must be signed in to change notification settings - Fork 19
Child process #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Child process #25
Changes from 11 commits
3744e2a
905514d
916cb5a
d8d487c
3d17a2a
0b660e6
12934bb
4534d1f
422cb44
fccbbfa
462f926
4a335c4
87c917f
b2a9b96
bfe611d
80f72ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| const { exec } = require('child_process'); | ||
|
|
||
| exec('ls -l', (err, stdout, stderr) => { | ||
| console.log(stdout); | ||
| }); | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| const fibonacci = (num) => num <= 1 ? 1 : fibonacci(num - 1) + fibonacci(num - 2) | ||
|
|
||
| process.on('message', ({n}) => { | ||
| process.send({ fib: fibonacci(n), n }) | ||
| // optional - there is no reason why this child process | ||
| // can't be called multiple times. | ||
| process.exit() | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| const { fork } = require('child_process') | ||
|
|
||
| const child1 = fork('fork-child') | ||
| const child2 = fork('fork-child') | ||
| const child3 = fork('fork-child') | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Semi-colons missing but present elsewhere |
||
|
|
||
| // send data to the child process to perform the calculation | ||
| child1.send({ n: 5 }); | ||
| child2.send({ n: 10 }); | ||
| child3.send({ n: 45 }); | ||
|
|
||
| child1.on('message', (m) => { | ||
| console.log('PARENT child1 got message:', m); | ||
| }); | ||
| child2.on('message', (m) => { | ||
| console.log('PARENT child2 got message:', m); | ||
| }); | ||
| child3.on('message', (m) => { | ||
| console.log('PARENT child3 got message:', m); | ||
| }); | ||
|
|
||
| child1.on('exit', () => { | ||
| console.log('child1 exited'); | ||
| }); | ||
| child2.on('exit', () => { | ||
| console.log('child2 exited'); | ||
| }); | ||
| child3.on('exit', () => { | ||
| console.log('child3 exited'); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,189 @@ | ||
| --- | ||
| layout: default | ||
| layout: post | ||
| title: Child Processes | ||
| url: child-processes | ||
| author: ian | ||
| --- | ||
|
|
||
| Coming soon. | ||
| <<<<<<< HEAD | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some merge artefact
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorted |
||
| _Prerequisites: [events](../events), [streams](../buffers-and-streams)_ | ||
|
|
||
| In the meantime, please see https://nodejs.org/api/child_process.html | ||
| The typical operating system has different processes running in the background, and each process is being managed by a single-core of our CPU and will run a series of calculations each time it is being ticked. To take full advantage of our CPU using a single process, we would need a number of processes that is at least equal to the number of cores in our CPU. In addition, each process might be responsible for running a series of calculations of different logic, which will give the end user a better control over the CPU’s behavior. | ||
|
|
||
| The `child_process` module provides the ability to spawn child processes and interact with other programs. Whilst single-threaded, non-blocking performance in Node.js is great for a single process, we can use `child_process` to handle the increasing workload in our applications in multiple threads. | ||
|
|
||
| Using multiple processes allows a Node application to scale. Node is designed for building distributed applications with many nodes, hence it's name Node. | ||
|
|
||
| Pipes for `stdin`, `stdout`, and `stderr` are established between the parent Node process and the spawned subprocess. The behaviour matches that of pipes in the shell. | ||
|
|
||
| The examples in this article are all Linux-based. On Windows, you will need to switch the commands I use with their Windows alternatives or use a Linux-like shell. | ||
|
|
||
| # `exec` vs `spawn` | ||
|
|
||
| There are two main approaches to running child processesL `exec` and `spawn`. | ||
|
|
||
| | `exec` | `spawn` | | ||
| |------------------------------------------------------------|-------------------------------------------------------------| | ||
| | spawns a shell in which the command is executed | Does not spawn a shell | | ||
| | buffers the data (waits until the process finishes and transfers all the data ) | Streams the data returned by the child process (data flow is constant) | | ||
| | maximum data transfer 200kb (by default | has no data transfer size limit | | ||
|
|
||
| Typically, `spawn` is more suitable for long-running process with large outputs. spawn streams input/output with child process. `exec` buffered output in a small (by default 200K) buffer. | ||
|
|
||
| ## A Simple `exec` | ||
|
|
||
| In this example, we will call the `ls` command to listwith the optional param `-l` to show a long list of details. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is |
||
| <div class="repl-code"> | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tiny nitpick: I'd adda line before the HTML code here to make it easier to scan
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
|
|
||
| ```javascript | ||
|
|
||
| const { exec } = require('child_process'); | ||
|
|
||
| exec('ls -l', (err, stdout, stderr) => { | ||
| console.log(stdout); | ||
| }); | ||
|
|
||
| ``` | ||
| </div> | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd maybe add a quick line saying what exec does here - may seem obvious but I think it helps to reinforce the point.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just pushed |
||
| For more details of the options that can be passed to `exec`, please see https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback | ||
|
|
||
| # spawn | ||
|
|
||
| The `spawn` method works by returning a new process. The process object that is returned has properties for each standard IO represented as a `Stream`: `.stdin` (`WriteStream`), `.stout` (`ReadStream`) and `.stderr` (`ReadStream`). | ||
|
|
||
| We will now implement the previous example using the `spawn` method: | ||
|
|
||
| <div class="repl-code"> | ||
|
|
||
| ```javascript | ||
| const { spawn } = require('child_process'); | ||
| const ls = spawn('ls', ['-l']); | ||
|
|
||
| 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}`); | ||
| }); | ||
|
|
||
| ``` | ||
| </div> | ||
|
|
||
| In this example we are listening to the `stout` and `stderr` streams for `data` events, as well as listening for a `close` event. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these events different from the exec approach?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes - exec buffers it to strints
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Strings. I'll add that :) |
||
|
|
||
| # spawn using pipes | ||
|
|
||
| We are going to run two child processes, and pipe the output from one as the input to another. We want to use `wc` (word count) to count the number of words in the output from the `ls` command we have been using so far. This is the same as running `ls -l | wc` on the Linux/Mac command line. | ||
|
|
||
| <div class="repl-code"> | ||
|
|
||
| ```javascript | ||
| const { spawn } = require('child_process'); | ||
| const ls = spawn('ls', ['-l']); | ||
| const wc = spawn('wc') | ||
|
|
||
| // pipe output from ls as input to wc | ||
| ls.stdout.pipe(wc.stdin) | ||
|
|
||
| wc.stdout.on('data', (data) => { | ||
| console.log(`wc stdout: ${data}`); | ||
| }); | ||
|
|
||
| wc.stderr.on('data', (data) => { | ||
| console.error(`wc stderr: ${data}`); | ||
| }); | ||
|
|
||
| wc.on('close', (code) => { | ||
| console.log(`wc child process wc exited with code ${code}`); | ||
| }); | ||
|
|
||
| ``` | ||
| </div> | ||
|
|
||
| # fork | ||
|
|
||
| `fork` is a special version of `spawn' that allows messages to be sent between the Node processes. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| Unfortunately, we are unable to run this example in the REPL because it requires separate files. So this example is best run locally on your favourite terminal. | ||
|
|
||
| In this example, we are going to create a child process that can receive a number, and then calculates the fibonacci of that number. This has been implemented inefficiently to illustrate long running processes. The `message` event is used to listen for requests, and the payload is destructed for a numerical value, `n`. | ||
|
|
||
| ```javascript | ||
|
|
||
| const fibonacci = (num) => num <= 1 ? 1 : fibonacci(num - 1) + fibonacci(num - 2) | ||
|
|
||
| process.on('message', ({ n }) => { | ||
| process.send({ fib: fibonacci(n), n }) | ||
| // optional - there is no reason why this child process | ||
| // can't be called multiple times. | ||
| process.exit() | ||
| }) | ||
|
|
||
| ``` | ||
|
|
||
| The parent process creates 3 child processes, and passes a range of numbers to them for calculating. | ||
|
|
||
| ```javascript | ||
| const { fork } = require('child_process') | ||
|
|
||
| const child1 = fork('fork-child') | ||
| const child2 = fork('fork-child') | ||
| const child3 = fork('fork-child') | ||
|
|
||
| // send data to the child process to perform the calculation | ||
| child1.send({ n: 5 }); | ||
| child2.send({ n: 10 }); | ||
| child3.send({ n: 45 }); | ||
|
|
||
| child1.on('message', (m) => { | ||
| console.log('PARENT child1 got message:', m); | ||
| }); | ||
| child2.on('message', (m) => { | ||
| console.log('PARENT child2 got message:', m); | ||
| }); | ||
| child3.on('message', (m) => { | ||
| console.log('PARENT child3 got message:', m); | ||
| }); | ||
|
|
||
| child1.on('exit', () => { | ||
| console.log('child1 exited'); | ||
| }); | ||
| child2.on('exit', () => { | ||
| console.log('child2 exited'); | ||
| }); | ||
| child3.on('exit', () => { | ||
| console.log('child3 exited'); | ||
| }); | ||
| ``` | ||
|
|
||
| ## ChildProcess Events | ||
|
|
||
| The `child` processes communicates by emitting events to let the `parent` know what is going on. | ||
|
|
||
| | Event Name | Reason For Emitting | | ||
| |--------------|-----------------------------------------------------------------------------| | ||
| | `disconnect` | The parent process manually calls `child.disconnect` | | ||
| | `error` | The process could not be spawned or killed. | | ||
| | `exit` | The exit code for the child and the optional signalthat was used to terminate it. When null, imples the child process exited normally. | | ||
| | `close` | The `stdio` streams of a child process get closed. | | ||
| | `message` | This `child` process uses the `send` method to communicate with the parent. | | ||
|
|
||
| ## A note on options | ||
|
|
||
| These processes all accept an optional options object that allow us to control the context that the processes are run within. These vary for each method, and are described in detail in the [node documentation for `child_process`](https://nodejs.org/api/child_process.html). | ||
|
|
||
| ## Summary | ||
|
|
||
| Parents `spawm`, `fork` or `exec` child processes, and communicate via events or pipes. | ||
|
|
||
| Take me to [cheat sheet]({{ "/cheatsheet/#child-process" | url }}) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd stick a full stop after the link here :) |
||
|
|
||
| ## Exercise | ||
|
|
||
| Create a child process for doing some manipulation of a file or URL, and build a parent process that controls a number of these processes in parallel. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this have a hidden suggested-solution? If too much to do, we can leave it for now.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah - I think there is more value in populating the other bits of content and we can come back to this, or maybe I can put a note in for someone to do one and put it up as a PR :)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added comment - thanks for all the comments and keeping me honest. Made a big improvement to this piece. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| const { spawn } = require('child_process'); | ||
| const ls = spawn('ls', ['-l']); | ||
|
|
||
| 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}`); | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| const { spawn } = require('child_process'); | ||
| const ls = spawn('ls', ['-l']); | ||
| const wc = spawn('wc') | ||
|
|
||
| // pipe output from ls as input to wc | ||
| ls.stdout.pipe(wc.stdin) | ||
|
|
||
| wc.stdout.on('data', (data) => { | ||
| console.log(`wc stdout: ${data}`); | ||
| }); | ||
|
|
||
| wc.stderr.on('data', (data) => { | ||
| console.error(`wc stderr: ${data}`); | ||
| }); | ||
|
|
||
| wc.on('close', (code) => { | ||
| console.log(`wc child process wc exited with code ${code}`); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this code have semi-colons?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorted!