Home > Web Front-end > JS Tutorial > body text

Detailed explanation of Node.js event loop (Event Loop) and thread pool_node.js

WBOY
Release: 2016-05-16 16:17:23
Original
1216 people have browsed it

Node’s “Event Loop” is the core of its ability to handle large concurrency and high throughput. This is the most magical part, according to which Node.js can basically be understood as "single-threaded", while also allowing arbitrary operations to be processed in the background. This article will clarify how the event loop works so that you can feel its magic.

Event-driven programming

To understand the event loop, you must first understand Event Driven Programming. It appeared in 1960. Nowadays, event-driven programming is heavily used in UI programming. One of the main uses of JavaScript is to interact with the DOM, so using an event-based API is natural.

Simply defined: Event-driven programming controls the flow of an application through events or changes in state. Generally implemented through event monitoring, once the event is detected (ie, the state changes), the corresponding callback function is called. Sound familiar? In fact, this is the basic working principle of the Node.js event loop.

If you are familiar with client-side JavaScript development, think about those .on*() methods, such as element.onclick(), which are used to combine with DOM elements to deliver user interaction. This working mode allows multiple events to be triggered on a single instance. Node.js triggers this pattern through EventEmitters (event generators), such as in the server-side Socket and "http" modules. One or more state changes can be triggered from a single instance.

Another common pattern is to express success and fail. There are generally two common implementation methods. The first is to pass the "Error exception" into the callback, usually as the first parameter to the callback function. The second one uses the Promises design pattern and has added ES6. Note* Promise mode uses a function chain writing method similar to jQuery to avoid deep nesting of callback functions, such as:

Copy code The code is as follows:

$.getJSON('/getUser').done(successHandler).fail(failHandler)

The "fs" (filesystem) module mostly adopts the style of passing exceptions into callbacks. Technically triggering certain calls, such as the fs.readFile() attached event, but the API is just to alert the user and express the success or failure of the operation. The choice of such an API is based on architectural considerations rather than technical limitations.

A common misconception is that event emitters are also inherently asynchronous when firing events, but this is incorrect. Below is a simple code snippet to demonstrate this.

Copy code The code is as follows:

function MyEmitter() {
EventEmitter.call(this);
}
util.inherits(MyEmitter, EventEmitter);

MyEmitter.prototype.doStuff = function doStuff() {
console.log('before')
emitter.emit('fire')
console.log('after')}
};

var me = new MyEmitter();
me.on('fire', function() {
console.log('emit fired');
});

me.doStuff();
// Output:
// before
// emit fired
// after

Note* If emitter.emit is asynchronous, the output should be
// before
// after
// emit fired


EventEmitter often behaves asynchronously because it is often used to notify operations that need to be completed asynchronously, but the EventEmitter API itself is fully synchronous. Listening functions can be executed asynchronously internally, but please note that all listening functions will be executed synchronously in the order they are added.

Mechanism overview and thread pool

Node itself relies on multiple libraries. One of them is libuv, the amazing library for handling asynchronous event queues and execution.

Node utilizes as much of the operating system kernel as possible to implement existing functions. Like generating response requests, forwarding connections and entrusting them to the system for processing. For example, incoming connections are queued through the operating system until they can be handled by Node.

You may have heard that Node has a thread pool, and you may wonder: "If Node will process tasks in order, why do we need a thread pool?" This is because in the kernel, not all tasks are processed in order. Executed asynchronously. In this case, Node.JS must be able to lock the thread for a certain period of time while operating so that it can continue executing the event loop without getting blocked.

The following is a simple example diagram to show its internal operating mechanism:


┌──────────────────────┐
╭──►│ timers timers │        └───────────┬───────────┘
│       ┌───────────┴───────────┐
│                                                                                                                                                                                                                                      pending callbacks                                           │          └──────────┬───────────┘                                                                                                                                                                           | │ │ │ POLL ││── Connections, │
│                                                                                                                                                                                                                                                        since │           ┌───────────┴───────────┐                                                                                                                                             ╰─── ┤ setImmediate └───────────────────────┘

There are some things that are difficult to understand about the internal workings of the event loop:

All callbacks will be preset via process.nextTick() at the end of one stage of the event loop (e.g., timer) and before transitioning to the next stage. This will avoid potential recursive calls to process.nextTick(), causing an infinite loop.
"Pending callbacks" are callbacks in the callback queue that will not be processed by any other event loop cycle (for example, passed to fs.write).

Event Emitter and Event Loop

Simplify interaction with the event loop by creating an EventEmitter. It is a generic wrapper that allows you to create event-based APIs more easily. How the two interact often leaves developers confused.

The following example shows that forgetting that an event is triggered synchronously may cause the event to be missed.

Copy code The code is as follows:

// After v0.10, require('events').EventEmitter is no longer needed
var EventEmitter = require('events');
var util = require('util');

function MyThing() {
EventEmitter.call(this);

doFirstThing();
this.emit('thing1');
}
util.inherits(MyThing, EventEmitter);

var mt = new MyThing();

mt.on('thing1', function onThing1() {
// Sorry, this event will never happen
});


The 'thing1' event above will never be captured by MyThing() because MyThing() must be instantiated before it can listen for events. Here's a simple workaround without adding any extra closures:
Copy code The code is as follows:

var EventEmitter = require('events');
var util = require('util');

function MyThing() {
EventEmitter.call(this);

doFirstThing();
setImmediate(emitThing1, this);
}
util.inherits(MyThing, EventEmitter);

function emitThing1(self) {
self.emit('thing1');
}

var mt = new MyThing();

mt.on('thing1', function onThing1() {
// Executed
});

The following solution will also work, but at the expense of some performance:

Copy code The code is as follows:

function MyThing() {
EventEmitter.call(this);

doFirstThing();
// Using Function#bind() will lose performance
setImmediate(this.emit.bind(this, 'thing1'));
}
util.inherits(MyThing, EventEmitter);


Another problem is triggering Error. Finding problems in your application is hard enough, but without the call stack (note *e.stack), debugging is nearly impossible. When Error is received by a remote asynchronous request, the call stack will be lost. There are two possible solutions: trigger synchronously or ensure that the Error is passed in together with other important information. The examples below demonstrate both solutions:
Copy code The code is as follows:

MyThing.prototype.foo = function foo() {
// This error will be triggered asynchronously
var er = doFirstThing();
if (er) {
//When triggered, a new error needs to be created to retain the on-site call stack information
setImmediate(emitError, this, new Error('Bad stuff'));
Return;
}

// Trigger error and handle it immediately (synchronously)
var er = doSecondThing();
if (er) {
This.emit('error', 'More bad stuff');
Return;
}
}


Assess the situation. When an error is triggered, it may be handled immediately. Or, it might be some trivial exception that can be easily handled, or dealt with later. In addition, passing Error through a constructor is not a good idea, because the constructed object instance is likely to be incomplete. The case where Error was thrown directly just now is an exception.

Conclusion

This article briefly discusses the inner workings and technical details of the event loop. It's all well thought out. Another article will discuss the interaction of the event loop with the system kernel and show the magic of NodeJS asynchronous operation.

source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template