js与Nodejs的单线程和异步--初探

时间:2023-01-20 17:44:53

Event Loop

  • Event Loop : 浏览器使用 Event Loop 来协调 DOM 事件、UI 渲染、脚本执行和网络事件等。
  • Task Queue (Event Queue) :每当 DOM 事件、计时器事件或者网络事件被触发时,它们的回调函数和 Context 都会被压入 Event Queue,而 Event Loop 则会从中取出回调函数并执行。

虽然JavaScript是单线程的(按顺序执行的主线程),但维护事件队列的是另一个线程(Event Loop)。每当某个事件被触发,它的回调函数就会等待在这个队列(Event Queue)里。

异步方法最主要有2点,一个是不阻塞当前代码的执行,另一个是有回调方法。即异步方法运行完可以通知主线程。

js与Nodejs的单线程和异步--初探

Event Loop有browsing contexts和 workers两种类型。一个event loop可以有一个或多个task queues来自同一个task source的所有tasks必须放入同一个task queue。

通常有4种task source:DOM manipulation task source,user interaction task source,networking task source和history traversal task source。

同一个task queue是按先进先出顺序执行的。浏览器可以根据task queue中task source的不同,给予task queue不同的优先级。即由浏览器决定挑哪一个task queue中的队首task执行。

 

      浏览器的三个常驻线程

浏览器是多线程的,它们在内核制控下相互配合以保持同步。一个浏览器至少实现三个常驻线程:JavaScript引擎线程,GUI渲染线程,浏览器事件触发线程(UI线程)。

1) javascript引擎是基于事件驱动单线程执行的。JS引擎一直等待着event loop中任务的到来,然后加以处理(只有当前函数执行栈执行完毕,才会去任务队列中取任务执行)。浏览器无论什么时候都只有一个JS线程在运行JS程序。

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

3) 事件触发线程,当一个事件被触发时该线程会把事件添加到任务队列的队尾,等待JS引擎的处理。这些事件可以来自JavaScript引擎当前执行的代码块调用setTimeout/ajax添加一个任务,也可以来自浏览器其他线程如鼠标点击添加的任务。但由于JS的单线程关系,所有这些事件都得排队等待JS引擎处理。

javascript要等主线程空了才会去查看子线程有没有回调内容。异步的任务执行的顺序是不固定的,主要看返回的速度。

function f() {
  console.log("foo");
  setTimeout(g, 0);
  console.log("baz");
  h();
}

function g() {
  console.log("bar");
}

function h() {
  console.log("blix");
}

f();
输出的结果为:foo 、baz 、 blix 、bar

 

浏览器的多线程技术

Web Worker(可开启独立运行在浏览器后台的js环境)

 

Nodejs的异步任务执行流程

js与Nodejs的单线程和异步--初探

用户写的代码是单线程的,但nodejs内部并不是单线程!

node.js的事件模型

Node.js不是用多个线程为每个请求执行工作的,相反而是它把所有工作添加到一个事件队列中,然后有一个单独线程,来循环提取队列中的事件。事件循环线程抓取事件队列中最上面的条目,执行它,然后抓取下一个条目。当执行长期运行或有阻塞I/O的代码时,注意这里:它不会被阻塞,会继续提取下一个事件,而对于被阻塞的事件Node.js会从线程池中取出一个线程来运行这个被阻塞的代码,同时把当前事件本身和它的回调事件一同添加到事件队列(callback嵌套callback)。

当Node.js事件队列中的所有事件都被执行完成时,Node.js应用程序终止。

 

Nodejs架构

js与Nodejs的单线程和异步--初探

        NodeJS的显著特点:异步机制、事件驱动。

点餐的厨房的例子:整个过程没有阻塞新用户的连接,也不需要维护连接。

基于这样的机制,理论上陆续有用户请求连接,NodeJS都可以进行响应,因此NodeJS能支持比Java、php程序更高的并发量。

虽然维护事件队列也需要成本,再由于NodeJS是单线程,事件队列越长,得到响应的时间就越长,并发量上去还是会力不从心。

RESTful API是NodeJS最理想的应用场景,可以处理数万条连接,本身没有太多的逻辑,只需要请求API,组织数据进行返回即可。