I have seen many posts about Event Loop, and the hit rate of this knowledge point is also very high in the interview process, because the Event Loop involves a lot of content. Still arrange oneself, comb carefully again, in order to deepen oneself understanding and memory.

Lead based

What are processes and threads

In early operating systems there was no concept of a thread; a process was the smallest unit of possession and independent operation, and the smallest unit of program execution. Task scheduling adopts the preemptive scheduling method of time slice rotation, and process is the smallest unit of task scheduling. Each process has its own independent piece of memory, so that the memory address of each process is isolated from each other.

Later, with the development of the computer, the CPU requirements are higher and higher, the switching overhead between processes is larger, has been unable to meet the requirements of more and more complex programs. Thus, thread was invented. Thread is a single sequence control flow in program execution, the smallest unit of program execution flow, and the basic unit of processor scheduling and dispatching. A process can have one or more threads that share the memory space of the program (that is, the memory space of the process in which it is running). A standard thread consists of a thread ID, the current instruction pointer PC, a register, and a stack. A process consists of memory space (code, data, process space, open files) and one or more threads.

When you open a Tab page, you create a process that can have multiple threads, such as a rendering thread, a JS engine thread, an HTTP request thread, and so on. When you make a request, you create a thread that can be destroyed when the request ends.

The difference between processes and threads

  1. Processes are the smallest unit of CPU resource allocation; Threads are the smallest unit of CPU scheduling
  2. A process consists of one or more threads, which are different execution paths of code in a process
  3. Processes are independent of each other, but threads in the same process share the program’s memory space (including code segments, data sets, heaps, etc.) and some process-level resources (such as open files and signals, etc.). Threads in one process are invisible to other processes
  4. Scheduling and switching: Thread context switching is much faster than process context switching

Single thread versus multi-thread

  • Single-threaded program: only one thread, code is executed sequentially, prone to code blocking (page suspension)
  • Multi-threaded program: there are multiple threads, independent running between threads, can effectively avoid code blocking, and improve the running performance of the program

Multithreading in the browser kernel versus single threading in the JS engine

To put it simply, the browser kernel is a visual image output by taking page content, organizing information (applying CSS), calculating and combining the final output, often referred to as a rendering engine. The browser kernel is multi-threaded. Under the control of the kernel, threads cooperate with each other to keep synchronization. A browser usually consists of GUI rendering threads, JavaScript engine threads, event-triggering threads, timing trigger threads, and asynchronous HTTP request threads, which are resident threads.

1. GUI rendering thread

  • Mainly responsible for page rendering, parsing HTML, CSS, building DOM tree, layout and drawing, etc.
  • This thread is executed when the interface needs to be redrawn or when a backflow is caused by some operation.
  • The thread andJS engine threadMutually exclusive, GUI rendering is suspended when the JS engine thread executes, and GUI rendering is executed when the task queue is idle.

JavaScript engine threads

  • Mainly responsible for handling JavaScript scripts and executing code.
  • It is mainly responsible for executing the events that are ready to be executed, that is, when the timer count ends, or when the asynchronous request succeeds and returns correctly, it will enter the task queue in sequence and wait for the execution of the JS engine thread.
  • The thread andGUI rendering threadMutually exclusive, when the JS engine thread executes the JavaScript script for too long, it will cause the page rendering to block.

3. The event triggers the thread

  • It is responsible for handing the prepared events to the JS engine thread for execution.

  • When the setTimeout timer counts out, ajax and other asynchronous requests succeed and trigger the callback function, or the user triggers the click event, the thread will add the ready events to the end of the task queue and wait for the JS engine thread to execute.

4. The timer triggers the thread

  • The thread responsible for executing functions such as asynchronous timers, such as setTimeout and setInterval.
  • When the main thread executes code in sequence, it encounters a timer and hands the timer to the thread for processing. When the count is complete,Event trigger threadEvents after counting are added to the end of the task queue and waitJS engine threadThe execution.

5. Asynchronous HTTP request threads

  • A thread responsible for executing asynchronous requests such as XMLHttpRequest.
  • When the main thread executes code in sequence and encounters an asynchronous request, it passes the function to the thread for processing. When it listens for a status code change, if there is a callback function,Event trigger threadThe callback function is added to the end of the task queue, waiting for the JS engine thread to execute.

Why is JavaScript single threaded

The single thread of JavaScript, relative to its purpose. As a browser scripting language, JavaScript’s primary purpose is to interact with users and manipulate the DOM. This means that it has to be single-threaded, which can cause complex synchronization problems. For example, if there are two threads of JavaScript at the same time, one thread adds content to a DOM node, and the other thread removes that node, which thread should the browser use?

To avoid complexity, JavaScript has been single-threaded since its inception, and this has been a core feature of the language and will not change. Therefore, to prevent unexpected results from rendering, the browser sets the GUI rendering thread and the JS engine to be mutually exclusive. The GUI thread is suspended when the JS engine executes, and GUI updates are stored in a queue until the JS engine thread is idle.

Event Loop in browser

Single threading means that all tasks need to be queued until the first one is finished before the next one can be executed. If the first task takes a long time, the second task has to wait forever. The designers of the JavaScript language realized that the main thread could simply ignore the IO device, suspend the pending task and run the next one first. Wait until the IO device returns the result, then go back and continue the pending task.

Thus, JavaScript divides all executing tasks into synchronous and asynchronous tasks. Synchronization tasks are executed on the main thread, forming an execution stack. Outside of the main thread, the event-triggering thread manages a task queue and places an event in the task queue whenever an asynchronous task has a running result. Once all synchronous tasks in the execution stack are completed (the JS engine is idle at this time), the system reads the task queue, adds runnable asynchronous tasks to the executable stack, and starts execution. As shown in the figure:

  • Synchronous and asynchronous tasks go to different execution “places”, synchronous tasks go to the main thread, asynchronous tasks go to the Event Table and register functions.
  • When the specified Event completes, the Event Table moves this function to the Event Queue.
  • If the tasks in the main thread are empty after execution, the Event Queue will read the corresponding function and enter the main thread for execution.
  • This process is repeated over and over again, known as an Event Loop.

Microtasks and macro tasks

In addition to the generalized synchronous and asynchronous tasks, we have a more detailed definition of tasks. Different Task sources are assigned to different Task queues. Task sources can be divided into microtasks and macrotasks. In the ES6 specification, microtasks are called Jobs and MacroTasks are called tasks.

  • Macro tasks: setTimeout, setInterval, setImmediate, Script (overall code in script tags), I/O operations, XHR, UI rendering, etc.
  • Microtasks: Promise, MutationObserver

The relationship between event loops, macro tasks and micro tasks is shown in the figure below:

So the order of Event Loop execution is as follows:

  • The synchronization code is performed first, which is a macro task
  • When all synchronous code has been executed, the execution stack is empty, and the query is made to see if any asynchronous code needs to be executed
  • Perform all microtasks
  • When all the microtasks are done, the page is rendered if necessary
  • Then start the next Event Loop, which executes the asynchronous code in the macro task, the callback function in setTimeout

A lot of people here have the misconception that micro tasks are faster than macro tasks, which is actually wrong. Because a macro task includes script, the browser executes a macro task first, followed by a microtask if there is asynchronous code.

RequestAnimationFrame and requestIdleCallback

In order to solve this problem, the W3C has defined a requestAnimationFrame method, which will be called back before the browser’s next drawing. RequestAnimationFrame is a callback that is executed before rendering is found at the end of each loop, not a macro/micro task.

RequestIdleCallback should be used when you are concerned about the user experience and don’t want users to feel stuck because of unimportant tasks such as statistics reporting. Because the requestIdleCallback method queues functions that are called during the browser’s idle time. This enables developers to perform background and low-priority work on the main event loop without affecting the delay of critical events such as animations and input responses. Functions are typically executed in first-come-first-called order; however, if a callback function specifies a timeout, it is possible to scramble the order of execution in order to execute the function before it times out.

Event Loop in Node

Node.js is also a single-threaded Event Loop, but it operates differently from the browser environment.

The Node Event Loop is divided into six stages, which are repeated in sequence. Each time a phase is entered, the function is fetched from the corresponding callback queue and executed. The next phase occurs when the queue is empty or the number of callback functions executed reaches a threshold set by the system. As shown below:

timer

The Timers phase performs setTimeout and setInterval callbacks and is controlled by the Poll phase. Similarly, the timer specified in Node is not the exact time and can only be executed as soon as possible.

I/O

The I/O phase handles a few unexecuted I/O callbacks from the previous cycle

Idle, prepare phase

Only used internally by node

poll

Poll is a crucial stage in which the system does two things.

  1. Go back to the Timer phase and perform the callback
  2. Perform I/O callback

And if the timer is not set when entering this phase, the following two things will happen

  • If the poll queue is not empty, the callback queue is traversed and executed synchronously until the queue is empty or the system limit is reached
  • If the poll queue is empty, two things happen
    • If you havesetImmediateThe callback needs to be executed, and the poll phase stops and goes to the Check phase to execute the callback
    • If there is nosetImmediateWhen a callback needs to be executed, it waits for the callback to be queued and executes immediately, again with a timeout set to prevent waiting forever

Of course, if the timer is set and the poll queue is empty, the poll queue will determine whether there is a timer timeout, and if so, the callback will be returned to the timer phase.

check

The Check phase performs setImmediate

close callbacks

Close The Close event is executed during the Callbacks phase

Finally, process.nextTick in Node is separate from the Event Loop. It has a queue of its own, and when each stage is complete, if there is a nextTick queue, it will empty all callback functions in the queue. And takes precedence over other Microtasks.

setTimeout(() = > {
 console.log('timer1')
 Promise.resolve().then(function() {
   console.log('promise1')})},0)
process.nextTick(() = > {
 console.log('nextTick')
 process.nextTick(() = > {
   console.log('nextTick')
   process.nextTick(() = > {
     console.log('nextTick')
     process.nextTick(() = > {
       console.log('nextTick')})})})})// nextTick=>nextTick=>nextTick=>nextTick=>timer1=>promise1
Copy the code

The Event Loop of Node is different from that of the browser

In the browser environment, the microTask queue is executed after each MacroTask has been executed. In Node.js, microTasks are executed between phases of the event cycle, i.e. tasks in the MicroTask queue are executed after each phase is completed.

The last

The level is limited, welcome to point wrong. After reading, if it helps you please like oh, if you have questions, please comment ~

References:

  • More on the Event Loop
  • Differences between browser and Node event loops
  • From browser multi process to JS single thread, JS running mechanism is the most comprehensive combing
  • This time, thoroughly understand the JavaScript execution mechanism
  • HTML Standard series: Event Loop, requestIdleCallback, and requestAnimationFrame