Home  >  Article  >  Web Front-end  >  Detailed introduction to JavaScript single-threading (picture)

Detailed introduction to JavaScript single-threading (picture)

黄舟
黄舟Original
2017-03-10 14:30:371209browse

Introducing in detail some things about single-threading in JavaScript (pictures)

Recently, I was asked by a classmate about some things about single-threading in JavaScript, but I couldn’t answer it. Okay, I feel like I learned JavaScript in vain. Here are some things I have compiled about JavaScript single-threading in the past few days.

First of all, let’s talk about why JavaScript is single-threaded?

As we all know, JavaScript runs in a single-threaded manner. When talking about threads, we naturally think of processes. So what's the connection between them?

Processes and threads are concepts of the operating system. A process is an execution instance of an application. Each process is composed of private virtual address space, code, data and other system resources; the process can apply to create and use system resources (such as independent memory areas, etc.) during operation. , these resources will also be destroyed when the process terminates. A thread is an independent execution unit within a process, and process resources can be shared between different threads. Therefore, in the case of multi-threading, special attention needs to be paid to access control of critical resources. After the system creates the process, it starts the main thread that executes the process, and the life cycle of the process is consistent with the life cycle of the main thread. The exit of the main thread means the termination and destruction of the process. The main thread is created by the system process, and users can also create other threads independently. This series of threads will run concurrently in the same process.

Obviously, parallel processing of applications can be achieved under multi-threaded operation, thereby improving the performance and throughput of the entire application with higher CPU utilization. Especially now that many languages ​​support multi-core parallel processing technology, JavaScript is executed in a single thread. Why?

In fact, this has something to do with its purpose. As a browser scripting language, JavaScript's main purpose is to interact with users and manipulate the DOM. If these DOMs are operated in a multi-threaded manner, operation conflicts may occur. Suppose there are two threads operating a DOM element at the same time. Thread 1 requires the browser to delete the DOM, while thread 2 requires the DOM style to be modified. At this time, the browser cannot decide which thread to use for the operation. Of course, we can introduce a "lock" mechanism into the browser to resolve these conflicts, but this will greatly increase the complexity, so JavaScript has chosen single-threaded execution since its birth.

In addition, because JavaScript is single-threaded, it can only execute one specific task at a certain time and will block the execution of other tasks. Then for time-consuming tasks such as I/O, there is no need to wait for them to complete before continuing with subsequent operations. Before these tasks are completed, JavaScript can proceed to perform other operations. When these time-consuming tasks are completed, the corresponding processing will be performed in the form of callbacks. These are the inherent features of JavaScript: asynchronous and callbacks.

Of course, for unavoidable time-consuming operations (such as heavy operations, multiple loops), HTML5 proposes Web Worker, which will use the Worker class to create an additional thread in the current JavaScript execution main thread. To load and run a specific JavaScript file, this new thread and the JavaScript main thread will not affect each other or block execution, and the Web Worker provides an interface for data exchange between this new thread and the JavaScript main thread: postMessage and onMessage events. However, the DOM cannot be manipulated in the HTML5 Web Worker. Any task that requires operating the DOM needs to be entrusted to the JavaScript main thread for execution. Therefore, although the HTML5 Web Worker is introduced, the single-threaded nature of JavaScript is still not changed.

Concurrency mode and Event Loop

JavaScript has a concurrency model based on "Event Loop".

Ah, concurrency? Isn’t it said that JavaScript is single-threaded? Yes, it is indeed single-threaded, but there is a difference between concurrency and parallelism. The former is logical simultaneity, while the latter is physical simultaneity. Therefore, single-core processors can also achieve concurrency.

Detailed introduction to JavaScript single-threading (picture)
Concurrency and parallelism

Parallel is easy for everyone to understand, and the so-called "concurrency" means that two or more events occur at the same time interval. As shown in the first table above, since the computer system has only one CPU, the three programs ABC use the CPU alternately from a "micro" perspective, but the alternation time is very short and the user cannot notice it, forming concurrency in a "macro" sense. operate.

Runtime Concept

The following content explains a theoretical model. Modern JavaScript engines have focused on implementing and optimizing several of the concepts described below.

Stack (Stack)

Here are the tasks that JavaScript is executing. Each task is called a stack of frames.

function f(b){
  var a = 12;
  return a+b+35;
}

function g(x){
  var m = 4;
  return f(m*x);
}

g(21);

When the above code calls g, it creates the first frame of the stack, which contains the parameters and local variables of g. When g calls f, the second frame will be created and placed on top of the first frame. Of course, this frame also contains the parameters and locals of f variable. When f returns, its corresponding frame will pop off the stack. In the same way, when g returns, the stack is empty (the specific definition of the stack is Last-in first-out (LIFO)).

Heap (Heap)

A name used to represent a large unstructured area in memory where objects are allocated.

Queue(Queue)

A JavaScript runtime contains a task queue, which is composed of a series of tasks to be processed. Each task has a corresponding function. When the stack is empty, a task will be taken from the task queue and processed. This process calls a series of functions associated with the task (thus creating an initial stack frame). When the task is processed, the stack will be empty again. (Queue is characterized by First-in First-out (FIFO)).

In order to facilitate description and understanding, the following conventions are made:

  • Stack stack is the main thread

  • Queue queue is the task queue (Waiting for scheduling to the main thread for execution)

OK, the above knowledge points help us clarify a concept related to JavaScript runtime, which will help with the following analysis.

Event Loop

It is called Event loop because it is implemented in a similar way:

while(queue.waitForMessage()){
  queue.processNextMessage();
}

As mentioned above, "task queue" is an event Queue, if the I/O device completes the task or the user triggers an event (the event specifies a callback function), then the relevant event processing function will enter the "task queue". When the main thread is idle, the third thread in the "task queue" will be scheduled. A pending task, (FIFO). Of course, for the timer, when the specified time is reached, the corresponding task will be inserted into the end of the "task queue".

"Execute to completion"

Whenever a task is executed, other tasks will be executed. That is, when a function runs, it cannot be replaced and will complete before other code runs.
Of course, this is also a shortcoming of Event Loop: when a task takes too long to complete, the application cannot process user interactions (such as click events) in time, and even causes the application to crash. A better solution is to shorten the task completion time, or divide a task into multiple tasks for execution as much as possible.

Never block

JavaScript is different from other languages. One of the characteristics of its Event Loop is that it never blocks. I/O operations are usually handled through events and callback functions. So, while your application is waiting for an indexedDB or XHR asynchronous request to return, it can still handle other operations (such as user input).

Exceptions do exist, such as alerts or synchronous XHR, but avoiding them is considered best practice. Note that exceptions to exceptions do exist (but usually due to implementation errors rather than other reasons).

Timer

Some concepts of timer

As mentioned above, when the specified time is reached, the timer will insert the corresponding callback function into the end of the "task queue" . This is the "timer" function.

The timer includes two methods: setTimeout and setInterval. Their second parameter specifies the number of milliseconds after which the callback function should be delayed.
There are the following things to note about the second parameter:

  • When the second parameter is defaulted, the default is 0;

  • When the specified value is less than 4 milliseconds, increase it to 4ms (4ms is specified by the HTML5 standard, and 10ms for browsers in 2010 and before);

If you understand With the above knowledge, the following code should be no problem for you:

console.log(1);
setTimeout(function(){
    console.log(2);
},10);
console.log(3);
// 输出:1 3 2

In-depth understanding of timers

Zero delay setTimeout(func, 0)

Zero delay does not mean that the callback function is executed immediately. It depends on whether the main thread is currently idle and the tasks waiting in front of it in the "task queue".

Look at the following code:

(function () {

  console.log('this is the start');

  setTimeout(function cb() {
    console.log('this is a msg from call back');
  });

  console.log('this is just a message');

  setTimeout(function cb1() {
    console.log('this is a msg from call back1');
  }, 0);

  console.log('this is the  end');

})();

// 输出如下:
this is the start
this is just a message
this is the end
undefined // 立即调用函数的返回值
this is a msg from callback
this is a msg from a callback1

The role of setTimeout(func, 0)

  • Let the browser render the current Changes (Many browser UI render and js execution are placed in a thread, and thread blocking will cause the interface to fail to update and render)

  • Re-evaluate the "scriptis running too long" warning

  • Change the execution order

Look at the following code again:

<button id=&#39;do&#39;> Do long calc!</button>
<p id=&#39;status&#39;></p>
<p id=&#39;result&#39;></p>

$(&#39;#do&#39;).on(&#39;click&#39;, function(){

  $(&#39;#status&#39;).text(&#39;calculating....&#39;);// 此处会触发redraw事件,但会放到队列里执行,直到long()执行完。

  // 没设定定时器,用户将无法看到“calculating...”
  long();// 执行长时间任务,造成阻塞

  // 设定了定时器,用户就如期看到“calculating...”
  //setTimeout(long,50);// 大约50ms后,将耗时长的long回调函数插入“任务队列”末尾,根据先进先出原则,其将在redraw之后被调度到主线程执行

 });

function long(){
  var result = 0
  for (var i = 0; i<1000; i++){
    for (var j = 0; j<1000; j++){
      for (var k = 0; k<1000; k++){
        result = result + i+j+k
      }
    } 
  }
  $(&#39;#status&#39;).text(&#39;calclation done&#39;); // 在本案例中,该语句必须放到这里,这将使它与回调函数的行为类似
}

The difference between the original and the replica setInterval

Everyone may know that setTimeout can imitate the effect of setInterval. Let’s take a look at the difference between the following codes:

// 利用setTimeout模仿setInterval
setTimeout(function(){
    /* 执行一些操作. */
    setTimeout(arguments.callee, 10);
}, 1000);

setInterval(function(){
    /* 执行一些操作 */
}, 1000);

Maybe you think there is no difference. Indeed, when the operation in the callback function takes a short time, you can't see any difference between them.

其实:上面案例中的 setTimeout 总是会在其回调函数执行后延迟 10ms(或者更多,但不可能少)再次执行回调函数,从而实现setInterval的效果,而 setInterval 总是 10ms 执行一次,而不管它的回调函数执行多久。

所以,如果 setInterval 的回调函数执行时间比你指定的间隔时间相等或者更长,那么其回调函数会连在一起执行。

你可以试试运行以下代码:

var counter = 0;
var initTime = new Date().getTime();
var timer = setInterval(function(){
    if(counter===2){
        clearInterval(timer);
    }
    if(counter === 0){
        for(var i = 0; i < 1990000000; i++){
            ;
        }
    }

    console.log("第"+counter+"次:" + (new Date().getTime() - initTime) + " ms");

    counter++;
},1000);

我电脑Chrome浏览器的输入如下:

第0次:2007 ms
第1次:2013 ms
第2次:3008 ms

浏览器

浏览器不是单线程的

上面说了这么多关于JavaScript是单线程的,下面说说其宿主环境——浏览器。

浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:

  1. javascript引擎线程 javascript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。

  2. GUI渲染线程 GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

  3. 浏览器事件触发线程 事件触发线程,当一个事件被触发时该线程会把事件添加到“任务队列”的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS是单线程执行的,所有这些事件都得排队等待JS引擎处理。

在Chrome浏览器中,为了防止因一个标签页奔溃而影响整个浏览器,其每个标签页都是一个进程。当然,对于同一域名下的标签页是能够相互通讯的,具体可看 浏览器跨标签通讯。在Chrome设计中存在很多的进程,并利用进程间通讯来完成它们之间的同步,因此这也是Chrome快速的法宝之一。对于Ajax的请求也需要特殊线程来执行,当需要发送一个Ajax请求时,浏览器会开辟一个新的线程来执行HTTP的请求,它并不会阻塞JavaScript线程的执行,当HTTP请求状态变更时,相应事件会被作为回调放入到“任务队列”中等待被执行。

看看以下代码:

document.onclick = function(){
    console.log("click")
}

for(var i = 0; i< 100000000; i++);

解释一下代码:首先向document注册了一个click事件,然后就执行了一段耗时的for循环,在这段for循环结束前,你可以尝试点击页面。当耗时操作结束后,console控制台就会输出之前点击事件的”click”语句。这视乎证明了点击事件(也包括其它各种事件)是由额外单独的线程触发的,事件触发后就会将回调函数放进了“任务队列”的末尾,等待着JavaScript主线程的执行。

总结

  • JavaScript是单线程的,同一时刻只能执行特定的任务。而浏览器是多线程的。

  • 异步任务(各种浏览器事件、定时器等)都是先添加到“任务队列”(定时器则到达其指定参数时)。当Stack栈(JS主线程)为空时,就会读取Queue队列(任务队列)的第一个任务(队首),然后执行。

JavaScript为了避免复杂性,而实现单线程执行。而今JavaScript却变得越来越不简单了,当然这也是JavaScript迷人的地方。


The above is the detailed content of Detailed introduction to JavaScript single-threading (picture). For more information, please follow other related articles on the PHP Chinese website!

Statement:
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