zoukankan      html  css  js  c++  java
  • Event Loop

    一. JavaScript单线程
    JavaScript单线程,也就是说,同一个时间只能做一件事。那么,为什么JavaScript不能有多个线程呢?作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
    所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。
    为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变JavaScript单线程的本质。
    那么,由于js单线程的特点,如果JS发起了一个异步IO请求,在等待结果返回的这个时间段,后面的代码都会被阻塞。 我们知道JS主线程和渲染进程是相互阻塞的,因此这就会造成浏览器假死。 如何解决这个问题? 一个有效的办法就是我们这节要讲的事件循环Event Loop。
     
    二. 什么是事件循环?
    js中所有任务分为两种:同步任务synchronous、异步任务asynchronous
    • 同步任务指的是:立即执行的任务,同步任务一般会直接进入到主线程中执行;
    • 异步任务指的是:异步执行的任务,比如 ajax 网络请求,setTimeout 定时函数等都属于异步任务,异步任务会通过任务队列的机制(先进先出的机制)来进行协调
    • 异步任务又分为:宏任务和微任务
    • 宏任务:
      • js(整体代码)
      • I/O、UI 渲染
      • MessageChannel、postMessage
      • setImmediate(Node.js 环境)
      • setTimeout、setInterval
      • requestAnimationFrame 属于 GUI 引擎,发生在渲染过程的重绘重排部分,在 UI 渲染之前执行
    • 微任务:
      • process.nextTick(Node.js 环境)
      • MutaionObserver(浏览器环境)
      • Promise的回调
    • js运行机制:
      1. 从宏任务的头部取出一个任务执行;
      2. 执行过程中若遇到微任务则将其添加到微任务的队列中;
      3. 宏任务执行完毕后,微任务的队列中是否存在任务,若存在,则挨个儿出去执行,直到执行完毕;
      4. GUI 渲染;
      5. 回到步骤 1,直到宏任务执行完毕;
    这 4 步构成了一个事件的循环检测机制,即我们所称的eventloop。
    三.宏任务和微任务执行顺序
     
    console.log(1);
    setTimeout(function() {
      console.log(2);
    }, 0);
    new Promise(function(resolve) {
      console.log(3);
      resolve(Date.now());
    }).then(function() {
      console.log(4);
    });
    console.log(5);
    setTimeout(function() {
      new Promise(function(resolve) {
        console.log(6);
        resolve(Date.now());
      }).then(function() {
        console.log(7);
      });
    }, 0);

    // 执行步骤如下:
    // 1.执行 log(1),输出 1;
    // 2.遇到 setTimeout,将回调的代码 log(2)添加到宏任务中等待执行;
    // 3.执行 console.log(3),将 then 中的 log(4)添加到微任务中;
    // 4.执行 log(5),输出 5;
    // 5.遇到 setTimeout,将回调的代码 log(6, 7)添加到宏任务中;
    // 6.宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,存在一个微任务 log(4)(在步骤 3 中添加的),执行输出 4;
    // 7.取出下一个宏任务 log(2)执行,输出 2;
    // 8.宏任务的一个任务执行完毕,查看微任务队列中是否存在任务,不存在;
    // 9.取出下一个宏任务执行,执行 log(6),将 then 中的 log(7)添加到微任务中;
    // 10.宏任务执行完毕,存在一个微任务 log(7)(在步骤 9 中添加的),执行输出 7;
    // 因此,最终的输出顺序为:1, 3, 5, 4, 2, 6, 7;
    // 参考 https://juejin.cn/post/6844904035770695693
     
    四. 调用栈call stack、消息队列Message queue、微任务队列 MicroTask queue
    1.js在函数之前执行之前,会创建执行上下文,函数在执行时,会被压入调用栈
    2.遇到异步任务,如果是宏任务,会将宏任务的回调函数,放入消息队列,等待执行
    3.如果是微任务,会将微任务的回调函数,放入到微任务队列,等待执行
    4.等调用栈清空,微任务队列中的任务,会依次压入调用栈执行并弹出 ,微任务队列清空后,消息队列中的任务,会依次压入调用栈执行并弹出
     
     
    五.Node.js中的事件循环
    1.node.js中的事件循环的特点:
    • Node.js 通过 libuv 来处理与操作系统的交互,并且因此具备了异步、非阻塞、事件驱动的能力。
    • Node.js 实际上是 Javascript 执行线程的单线程,真正的的 I/O 操作,底层 API 调用都是通过多线程执行的。
    • CPU 密集型的任务是 Node.js 的软肋。
    • 参考:https://developer.aliyun.com/article/3203
    2.和 browser事件循环 的不同点
    node.js中的事件循环,表现出的状态与浏览器大致相同。
    不同的是 node 中有一套自己的模型。node 中事件循环的实现依赖 libuv 引擎。
    • node.js中的Event Loop的单次循环是分阶段进行的。每个阶段运行完所有该阶段的回调函数或回调次数达到了次数限制,才会进入下一个阶段或指定的阶段,直到运行完最后一个阶段,进入下一个循环。
    • 除了Poll阶段,node会在每个阶段,将该阶段对应的所有宏任务都依次执行完,然后执行微任务队列中的所有任务。(注意:node.js 11.0及以后的版本中,Goole为了向浏览器靠齐,将这一行为改成与浏览器一致,即每个 Macrotask(setTimeout,setInterval和setImmediate) 执行完后,就去执行 Microtask 了)
    下面例子中的代码是按照最新的去进行分析的。
     
    3.事件循环模型
    4.nodejs中事件循环执行顺序
    node中事件循环的顺序
    外部输入数据 --> 轮询阶段(poll) --> 检查阶段(check) --> 关闭事件回调阶段(close callback) --> 定时器检查阶段(timer) --> I/O 事件回调阶段(I/O callbacks) --> 闲置阶段(idle, prepare) --> 轮询阶段...
    这些阶段大致的功能如下:
    • 定时器检测阶段(timers): 这个阶段执行定时器队列中的回调如 setTimeout() 和 setInterval()。
    • I/O事件回调阶段(I/O callbacks): 这个阶段执行几乎所有的回调。但是不包括close事件,定时器和setImmediate()的回调。
    • 闲置阶段(idle, prepare): 这个阶段仅在内部使用,可以不必理会
    • 轮询阶段(poll): 等待新的I/O事件,node在一些特殊情况下会阻塞在这里。
    • 检查阶段(check): setImmediate()的回调会在这个阶段执行。
    • 关闭事件回调阶段(close callbacks): 例如socket.on('close', ...)这种close事件的回调
    poll:
    这个阶段是轮询时间,用于等待还未返回的 I/O 事件,比如服务器的回应、用户移动鼠标等等。
    这个阶段的时间会比较长。如果没有其他异步任务要处理(比如到期的定时器),会一直停留在这个阶段,等待 I/O 请求返回结果。
    check:
    该阶段执行setImmediate()的回调函数。
    close:
    该阶段执行关闭请求的回调函数,比如socket.on('close', ...)。
    timer阶段:
    这个是定时器阶段,处理setTimeout()和setInterval()的回调函数。进入这个阶段后,主线程会检查一下当前时间,是否满足定时器的条件。如果满足就执行回调函数,否则就离开这个阶段。
    I/O callback阶段:
    除了以下的回调函数,其他都在这个阶段执行:
    • setTimeout()和setInterval()的回调函数
    • setImmediate()的回调函数
    • 用于关闭请求的回调函数,比如socket.on('close', ...)
     

    一点一滴累积,总有一天你也会成为别人口中的大牛!
  • 相关阅读:
    webkit and the browser
    【技术原创】京东商城价格图片分析解析源代码下载(C#),附演示程序 转
    maglev need 64bit linux version
    sed awk
    VMware公司SpringSource部门宣布收购Gemstone Systems公司
    新手?为什么需要关怀?
    【技术原创】京东商城价格图片分析解析源代码下载(C#),附演示程序 转
    【技术原创】京东商城价格图片分析解析源代码下载(C#),附演示程序 转
    C语言正则表达式库pcre介绍
    人工翻译
  • 原文地址:https://www.cnblogs.com/fancyLee/p/10935631.html
Copyright © 2011-2022 走看看