Detailed explanation of JavaScript running mechanism and event polling mode of NodeJS

1. Why is JavaScript single-threaded?

A major feature of the JavaScript language is that it is single-threaded, that is, only one thing can be done at a time. So, why can't JavaScript have multiple threads? This can improve efficiency.

JavaScript's single thread, relative to its purpose. As a browser scripting language, JavaScript's primary use is to interact with the user, and to manipulate the DOM. This determines that it can only be a single thread, otherwise it will bring complex synchronization problems. For example, assuming that JavaScript has two threads at the same time, one thread adds content to a DOM node, and the other thread deletes this node, which thread should the browser take?

So, to avoid complexity, JavaScript has been single-threaded since its inception, which has been a core feature of the language and will not change in the future.

In order to take advantage of the computing power of multi-core CPUs, HTML5 proposes the Web Worker standard, which allows JavaScript scripts to create multiple threads, but the sub-threads are completely controlled by the main thread and must not operate the DOM. So, this new standard doesn't change the single-threaded nature of JavaScript.

The task queue

Single-threaded means that all tasks need to be queued, and the next task will be executed after the previous task ends. If the former task takes a long time, the latter task has to wait forever.

If the queuing is due to the large amount of calculation and the CPU is too busy, it’s okay, but many times the CPU is idle, because the IO devices (input and output devices) are very slow (such as Ajax operations to read data from the network), you have to Wait for the result to come out, and then proceed to the next execution.

The designers of the JavaScript language realized that at this time, the main thread can completely ignore the IO device, suspend the waiting tasks, and run the later tasks first. Wait until the IO device returns the result, then go back and continue to execute the suspended task.

Therefore, all tasks can be divided into two types, one is a synchronous task (synchronous), and the other is an asynchronous task (asynchronous). Synchronous tasks refer to tasks queued for execution on the main thread, and the latter task can only be executed after the previous task is executed; asynchronous tasks refer to tasks that do not enter the main thread but enter the "task queue" (task queue) Task, only the "task queue" notifies the main thread that an asynchronous task can be executed, the task will enter the main thread for execution.

Specifically, the operation mechanism of asynchronous execution is as follows. (The same goes for synchronous execution, as it can be thought of as asynchronous execution without asynchronous tasks.)

(1) All synchronization tasks are executed on the main thread, forming an execution context stack.

(2) In addition to the main thread, there is also a "task queue" (task queue). As soon as the asynchronous task has a running result, an event is placed in the "task queue".

(3) Once all the synchronization tasks in the "execution stack" are executed, the system will read the "task queue" to see what events are in it. Those corresponding asynchronous tasks then end the waiting state, enter the execution stack, and start execution.

(4) The main thread keeps repeating the third step above.

The following figure is a schematic diagram of the main thread and task queue.

In-depth understanding: detailed explanation of JavaScript running mechanism and event polling mode of NodeJS

As long as the main thread is empty, it will read the "task queue", which is how JavaScript works. This process will keep repeating.

3. Events and callback functions

"Task queue" is a queue of events (which can also be understood as a queue of messages). When an IO device completes a task, an event is added to the "task queue", indicating that the related asynchronous task can enter the "execution stack". The main thread reads the "task queue", which is to read what events are in it.

The events in the "task queue", in addition to the events of the IO device, also include some user-generated events (such as mouse clicks, page scrolling, etc.). As long as the callback function is specified, these events will enter the "task queue" when they occur, waiting for the main thread to read.

The so-called "callback function" (callback) is the code that will be suspended by the main thread. An asynchronous task must specify a callback function. When the main thread starts to execute an asynchronous task, the corresponding callback function is executed.

The "task queue" is a first-in, first-out data structure, and the events in the front are read first by the main thread. The reading process of the main thread is basically automatic. As soon as the execution stack is emptied, the first event on the "task queue" will automatically enter the main thread. However, due to the "timer" function mentioned later, the main thread must first check the execution time, and some events can only return to the main thread after the specified time.

Fourth, the event polling (Event Loop)

The main thread reads events from the "task queue", and this process is cyclical, so the entire operating mechanism is also called Event Loop.

To better understand the Event Loop, take a look at the figure below (quoted from Philip Roberts' speech "Help, I'm stuck in an event-loop").

In-depth understanding: detailed explanation of JavaScript running mechanism and event polling mode of NodeJS

In the above figure, when the main thread runs, a heap and a stack are generated. The code in the stack calls various external APIs, and they add various events (click, load, done) to the "task queue". As long as the code in the stack is executed, the main thread will read the "task queue" and execute the callback functions corresponding to those events in turn.

The code on the execution stack (synchronous tasks) is always executed before reading the "task queue" (asynchronous tasks). See the example below.

var req = new XMLHttpRequest(); req.open('GET', url);
 req.onload = function (){}; req.onerror = function (){}; req.send();

The req.send method in the above code is an Ajax operation to send data to the server. It is an asynchronous task, which means that the system will read the "task queue" only after all the code of the current script is executed. Therefore, it is equivalent to the following notation.

var req = new XMLHttpRequest(); req.open('GET', url); req.send();
req.onload = function (){}; req.onerror = function (){};

That is to say, the part of the specified callback function (onload and onerror) does not matter before or after the send() method, because they are part of the execution stack, and the system always executes them before reading the "task queue" ".

5. Timer

In addition to placing events for asynchronous tasks, the "task queue" can also place timed events, which specify how long after certain code executes. This is called a "timer" function, which is code that executes regularly.

The timer function is mainly completed by the two functions setTimeout() and setInterval(). Their internal operating mechanisms are exactly the same. The difference is that the code specified by the former is executed once, while the latter is executed repeatedly. The following mainly discusses setTimeout().

setTimeout() accepts two parameters, the first is the callback function and the second is the number of milliseconds to delay execution.

console.log(1);setTimeout(function(){console.log(2);},1000);
console.log(3);

The execution result of the above code is 1, 3, 2 because setTimeout() delays the execution of the second line until 1000 milliseconds later.

If the second parameter of setTimeout() is set to 0, it means that the specified callback function will be executed (0 millisecond interval) immediately after the current code is executed (the execution stack is emptied).

setTimeout(function(){console.log(1);}, 0);console.log(2);

The execution result of the above code is always 2, 1, because only after the second line is executed, the system will execute the callback function in the "task queue".

In short, the meaning of setTimeout(fn,0) is to specify that a task is executed at the earliest available idle time of the main thread, that is, as early as possible. It adds an event to the end of the "task queue", so it won't get executed until both the sync task and the "task queue"'s existing events have been processed.

The HTML5 standard specifies the minimum value (the shortest interval) of the second parameter of setTimeout(), which must not be lower than 4 milliseconds. If it is lower than this value, it will automatically increase. Prior to this, older browsers set the minimum interval to 10 milliseconds. Also, those DOM changes (especially those involving page re-rendering) are usually not performed immediately, but every 16ms. At this time, the effect of using requestAnimationFrame() is better than setTimeout().

It should be noted that setTimeout() only inserts the event into the "task queue", and the main thread must wait until the current code (execution stack) is executed before executing the callback function it specifies. If the current code takes a long time, it may take a long time, so there is no way to guarantee that the callback function will be executed at the time specified by setTimeout().

6. Event Loop of Node.js

Node.js is also a single-threaded Event Loop, but its operating mechanism is different from the browser environment.

See the schematic below (by @BusyRich).

In-depth understanding: detailed explanation of JavaScript running mechanism and event polling mode of NodeJS

According to the above figure, the operating mechanism of Node.js is as follows.

(1) The V8 engine parses the JavaScript script.

(2) The parsed code calls the Node API.

(3) The libuv library is responsible for the execution of the Node API. It assigns different tasks to different threads to form an Event Loop (event loop), and returns the execution result of the task to the V8 engine in an asynchronous manner.

(4) The V8 engine returns the result to the user.

In addition to the two methods setTimeout and setInterval, Node.js also provides two other methods related to "task queue": process.nextTick and setImmediate. They can help us deepen our understanding of "task queues".

The process.nextTick method can trigger the callback function at the end of the current "execution stack" - before the next Event Loop (the main thread reads the "task queue"). That is, the task it specifies always happens before all asynchronous tasks. The setImmediate method adds an event to the end of the current "task queue", that is, the task it specifies is always executed in the next Event Loop, which is very similar to setTimeout(fn, 0). See the example below (via StackOverflow).

process.nextTick(function A() { console.log(1);
 process.nextTick(function B(){console.log(2);});});
setTimeout(function timeout() { console.log('TIMEOUT FIRED');
}, 0)// 1// 2// TIMEOUT FIRED

In the above code, because the callback function specified by the process.nextTick method is always triggered at the end of the current "execution stack", not only function A is executed before the callback function timeout specified by setTimeout, but also function B is executed before timeout. This means that if there are multiple process.nextTick statements (whether they are nested or not), they will all be executed on the current "execution stack".

Now, look at setImmediate again.

setImmediate(function A() { console.log(1);
setImmediate(function B(){console.log(2);});});
setTimeout(function timeout() { console.log('TIMEOUT FIRED');}, 0);

In the above code, setImmediate and setTimeout(fn,0) each add a callback function A and timeout, both of which are triggered in the next Event Loop. So, which callback function executes first? The answer is not sure. The running result may be 1--TIMEOUT FIRED--2, or TIMEOUT FIRED--1--2.

Confusingly, the Node.js documentation says that the callback function specified by setImmediate always precedes setTimeout. In fact, this only happens when recursive calls are made.

setImmediate(function (){ setImmediate(function A() { console.log(1);
 setImmediate(function B(){console.log(2);}); });
setTimeout(function timeout() { console.log('TIMEOUT FIRED'); }, 0);
});// 1// TIMEOUT FIRED// 2

In the above code, setImmediate and setTimeout are encapsulated in a setImmediate, and its running result is always 1--TIMEOUT FIRED--2. At this time, function A must be triggered before timeout. As for the second row after TIMEOUT FIRED (that is, function B is triggered after timeout), it is because setImmediate always registers the event to the next round of Event Loop, so function A and timeout are executed in the same round of Loop, and function B is executed in the next round of Loop. Round Loop execution.

From this, we get an important difference between process.nextTick and setImmediate: multiple process.nextTick statements are always executed once in the current "execution stack", and multiple setImmediate may need multiple loops to execute. In fact, that's why Node.js version 10.0 added the setImmediate method, otherwise a recursive call to process.nextTick like the one below would be endless and the main thread would never read the "event queue"!

process.nextTick(function foo() { process.nextTick(foo);});

In fact, now if you write recursive process.nextTick, Node.js will throw a warning asking you to change to setImmediate.

In addition, since the callback function specified by process.nextTick is triggered in this "event loop", and the setImmediate specified is triggered in the next "event loop", it is obvious that the former always occurs earlier than the latter, and the execution efficiency Also high (because the "task queue" is not checked).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326187325&siteId=291194637