140 lines
6.6 KiB
Markdown
140 lines
6.6 KiB
Markdown
![]() |
---
|
||
|
tags:
|
||
|
- node-js
|
||
|
---
|
||
|
|
||
|
# The Event Loop
|
||
|
|
||
|
Node.js provides a single-threaded asynchronous architecture which is achieved
|
||
|
via means of the Event Loop.
|
||
|
|
||
|
## Multi-threaded synchronous architectures
|
||
|
|
||
|
In the context of backend, a thread as an instance of a request-response
|
||
|
transaction.
|
||
|
|
||
|
For example a request is made from the client for a resource contained in a
|
||
|
database. The back-end language is an intermediary between the client machine
|
||
|
and the server. It receives the request and returns the resource as a response.
|
||
|
|
||
|
Many backend frameworks are synchronous but multithreaded. This means that a
|
||
|
thread can only process one request-response cycle at a time. The thread cannot
|
||
|
initiate a new cycle until it has finished with its current cycle.
|
||
|
|
||
|
If there was only one thread, this would be inefficient and unworkable.
|
||
|
Therefore the framework will be multi-threaded: multiple request-response cycles
|
||
|
can be executed at once by different threads.
|
||
|
|
||
|

|
||
|
|
||
|
To accomodate the ability to increase the scale of synchronous applications you
|
||
|
need to be able to spawn more threads commensurate to increased demand. This
|
||
|
increases the resource consumption of the framework (more cores, more memory
|
||
|
etc). Moreover it is possible to reach a point where all threads are active and
|
||
|
no more can be spawned. In this case there will simply be delays in the return
|
||
|
of data.
|
||
|
|
||
|
## Node as a single-threaded asynchronous architecture
|
||
|
|
||
|
In contrast, Node only has a single thread but it works asynchronously, not
|
||
|
synchronously. Thus it has a **single-threaded asynchronous architecture**. This
|
||
|
means whilst there is only a single thread it can juggle responses by
|
||
|
dispatching them asynchronously. When a request is made it sends it off and
|
||
|
continues with its execution and handling new requests. Once these resolve, the
|
||
|
data is returned to the main thread.
|
||
|
|
||
|

|
||
|
|
||
|
## The Event Loop
|
||
|
|
||
|
Node implements its single-threaded asynchronous architecture through the Event
|
||
|
Loop.
|
||
|
|
||
|
This is the mechanism by which Node keeps track of incoming requests and their
|
||
|
fulfillment status: whether the data has been returned successfully or if there
|
||
|
has been error.
|
||
|
|
||
|
Node is continually monitoring the Event Loop in the background.
|
||
|
|
||
|
A running Node application is a single running process. Like everything that
|
||
|
happens within the OS, a process is managed by the
|
||
|
[kernel](The_kernel.md) that dispatches operations to the CPU
|
||
|
in a clock cycle. A thread is a sequence of code that resides within the process
|
||
|
and utilises its memory pool (the amount of memory assigned by the kernel to the
|
||
|
Node process). The Event Loop runs on CPU ticks: a tick is a single run of the
|
||
|
Event Loop.
|
||
|
|
||
|
### Phases of the Event Loop
|
||
|
|
||
|
The Event Loop comprises six phases. The Event Loop starts at the moment Node
|
||
|
begins to execute your `index.js` file or any other application entry point.
|
||
|
These six phases create one cycle, or loop, equal to one **tick**. A Node.js
|
||
|
process exits when there is no more pending work in the Event Loop, or when
|
||
|
`process.exit()` is called manually. A program only runs for as long as there
|
||
|
are tasks queued in the Event Loop, or present on the
|
||
|
[call stack](Call_stack.md).
|
||
|
|
||
|

|
||
|
|
||
|
The phases are as follows:
|
||
|
|
||
|
1. **Timers**
|
||
|
- These are functions that execute callbacks after a set period of time. As
|
||
|
in standard JavaScript there are two global timer functions: `setTimeout`
|
||
|
and `setInterval`. Interestingly these are not core parts of the JavaScript
|
||
|
language, they are something that are made available to JS by the
|
||
|
particular browser. As Node does not run in the browser, Node has to
|
||
|
provide this functionality. It does so through the core `timers` module.
|
||
|
- At the beginning of this phase the Event Loop updates its own time. Then it
|
||
|
checks a queue, or pool, of timers. This queue consists of all timers that
|
||
|
are currently set. The Event Loop takes the timer with the shortest wait
|
||
|
time and compares it with the Event Loop's current time. If the wait time
|
||
|
has elapsed, then the timer's callback is queued to be called once the
|
||
|
[call stack](Call_stack.md) is empty.
|
||
|
2. **I/O Callbacks**
|
||
|
- Once timers have been checked and scheduled, Node jumps to I/O operations.
|
||
|
- Node implements a non-blocking input/output interface. This is to say,
|
||
|
writing and reading to disk (files in the Node application directory) is
|
||
|
implemented asynchronously. The asynchronous I/O request is recorded into
|
||
|
the queue and then the call stack continues.
|
||
|
3. **Idle / waiting / preparation**
|
||
|
- This phase is internal to Node and is not accessible to the programmer.
|
||
|
- It is primarily used for gathering informtion, and planning what needs to
|
||
|
be executed during the next tick of the Event Loop
|
||
|
4. **I/O polling**
|
||
|
- This is the phase at which the main block of code is read and executed by
|
||
|
Node.
|
||
|
- During this phase the Event Loop is managing the I/O workload, calling the
|
||
|
functions in the queue until the queue is empty, and calculating how long
|
||
|
it should wait until moving to the next phase. All callbacks in this phase
|
||
|
are called synchronously (although they return asynchronously) in the order
|
||
|
that they were added to the queue, from oldest to newest.
|
||
|
- This is the phase that can potentially block our application if any of
|
||
|
these callbacks are slow or do not return asynchronously.
|
||
|
5. **`setImmediate` callbacks**
|
||
|
- This phase runs as soon as the poll phase becomes idle. If `setImmediate()`
|
||
|
is scheduled within the I/O cycle it will always be executed before other
|
||
|
timers regardless of how many timers are present.
|
||
|
- This is your opportunity to grant precedence to certain threads within the
|
||
|
Node process
|
||
|
6. **Close events**
|
||
|
- This phase occurs when the Event Loop is wrapping up one cycle and is ready
|
||
|
to move to the next one.
|
||
|
- It is an opportunity for clean-up and to guard against memory leaks.
|
||
|
- This phase can be targetted via the `process.exit()` function or the close
|
||
|
event of a web-socket.
|
||
|
|
||
|
## Event _loop_ and event _queue_
|
||
|
|
||
|
The terms _event loop_ and _event queue_ are often used interchangeably in the
|
||
|
literature but in fact they are distinct.
|
||
|
|
||
|
The Event Loop is the Node runtime's method of execution, the queue is the stack
|
||
|
of tasks that are lined up and executed by the loop. We can think of the queue
|
||
|
as being the input and the loop as what acts on the input. The queue obviously
|
||
|
emerges from the program we write but it is scheduled, organised and sequenced
|
||
|
by the loop.
|
||
|
|
||
|
https://blog.appsignal.com/2022/07/20/an-introduction-to-multithreading-in-nodejs.html
|
||
|
https://school.geekwall.in/p/Bk2xFs1DV
|