zoukankan      html  css  js  c++  java
  • JavaScript Concurrency model and Event Loop 并发模型和事件循环机制

    原文地址:https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop

    JavaScript 有一个基于 event loop 的并发模型,这个模型和其他如 Java 和 C 语言的模型是不同的。

    Runtime concepts 运行时概念

    下面是一个可视化的模型展示,现代 JavaScript 引擎实现并着重优化了这个模型。

    Stack 栈

    函数从一个调用栈中执行,这个栈由多个调用栈帧组成

    function foo(b) {
      var a = 10;
      return a + b + 11;
    }
    
    function bar(x) {
      var y = 3;
      return foo(x * y);
    }
    
    console.log(bar(7)); //returns 42

    调用 bar 时,第一个调用帧被创建,这个帧包含 bar 的参数和局部变量,在 bar 内部调用 foo,第二个调用帧被创建,然后入栈,放在第一个调用帧的上面。

    当 foo 执行完成以后,栈顶的帧就会出栈。当 bar 也执行完成之后,栈为空。

    Heap 堆

    对象被分配到一个堆中,堆这个名字就象征着一大块没有结构的内存区域。

    Queue 队列

    一个 JavaScript 运行时有一个消息队列(message queue),这是一个待处理消息列表,每个消息有相应的函数会被调用来处理这个消息。

    在 event loop 的某个时刻,运行时开始处理队列中的消息,从最早的那一个开始。这个消息将被移出队列,对应的函数会被调用,并将这个消息作为参数。

    同样和前面一样,这个函数调用也会给这个函数产生一个调用栈帧。

     函数执行的过程会一直持续到调用栈再次为空,然后 event loop 会继续处理消息队列中的下一个消息 (如果有的话)

    Event loop 事件循环

    事件循环的实现原理类似于下面的伪代码:

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

    如果当前没有事件消息,queue.waitForMessage() 会同步地等待

    Run-to-completion 执行到完成

    在其它消息被处理之前,当前的消息会完全处理结束。这在你分析自己的代码时有非常好的特性,比如当一个函数执行时,它不会被提前阻止,在其它代码运行之前会完整地执行(这些代码可能会改变此函数操作的数据)。这个 C 语言不同,例如,当一个线程中的一个函数运行,它可能会被中止,因为运行时要去处理其它线程中的另外一些代码。

    这个模型的一个缺点就是,如果一个消息需要过长的处理时间,web 应用将无法处理其它如点击或者鼠标滚动事件。浏览器会弹出对话框“程序需要过长事件无法运行”的对话框来处理这种消息。一个好的实践是将消息处理时长减少,如果可以,将一个消息分成多个消息。

    Adding message 添加消息

    在 web 浏览器中,只要一个事件发生并且有一个事件监听绑定到这个事件,就会添加消息到队列中。如果没有监听到,事件就丢失了。

    函数 setTimeout 有两个参数,一个将添加到队列中的消息,一个代表延迟的时间值(毫秒,默认为0)。这个时间代表添加到队列前的最少等待时间。

    如果队列中没有其它消息,则这个消息将在延迟之后立即被处理。然而,如果有其它消息,setTimeout 的消息还要等待其它消息处理。所以第二个参数仅仅代表最少时间,并不保证确切的时间长度。

    下面是一个例子

    const s = new Date().getSeconds();
    
    setTimeout(function() {
      // prints out "2", meaning that the callback is not called immediately after 500 milliseconds.
      console.log("Ran after " + (new Date().getSeconds() - s) + " seconds");
    }, 500);
    
    while(true) {
      if(new Date().getSeconds() - s >= 2) {
        console.log("Good, looped for 2 seconds");
        break;
      }
    }

    Zero delays 零延时

    延时设置为 0 并不表示回调函数会立即执行,是否执行取决于队列中等待的任务的数量。

    在下面的例子中,cb1 是最后执行的,因为它要等待队列前面的任务执行完毕。

    (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"
    // note that function return, which is undefined, happens here 
    // "this is a msg from call back"
    // "this is a msg from call back1"

    几个运行时之间通信

    一个 web worker 或者 一个跨域的 iframe 都要自己的栈、堆和消息队列。

    两个不同的运行时只能通过 postMessage 方法进行通信。

    另外一个运行时如果监听 message 时间,就可以添加消息到自己的队列。

    Never blocking 非阻塞

    时间循环机制的一个非常有趣的特性的就是永远不会阻塞。

    处理 I/O 事件通常都通过事件和回调函数执行。当应用在等待一个数据库查询或者 XHR 请求的结果时,可以同时处理其它事情比如用户输入。

    遗留问题也是存在的,比如 alert 或者 同步的XHR,不过好的实践是避免使用它们。

  • 相关阅读:
    P1092 虫食算
    P1040 加分二叉树
    cfER76 abcd
    cf599 div2 a/b1/b2/c
    AtCoder Contest 144 DE
    Round G 2019
    luogu3084 Photo 单调队列优化DP
    luogu4234 最小差值生成树
    luogu1373 小a和uim之大逃离
    luogu1070 道路游戏 单调队列
  • 原文地址:https://www.cnblogs.com/xiyouchen/p/10302580.html
Copyright © 2011-2022 走看看