zoukankan      html  css  js  c++  java
  • 【杂谈】JS相关的线程模型整理

    1.JS是单线程吗?

    是的,到目前为止JS语言没有多线程的API,它的执行引擎只支持单线程,也就是一个JavaScript进程内只有一个线程。

    2.与DOM的交互为什么不可以是多线程?

    我觉得是可以的,但是如果有多线程可以操作DOM,则必须引入一个同步机制来保证线程安全。想象一个这样一个场景,用户提交了两个Append操作,而这两个操作由两个线程并行处理,如果没有同步机制,则可能出现下面这种执行时序,最终的效果就是线程A的修改丢失了,这肯定不是用户想要的效果。为了解决这个问题你需要对该节点加一个锁来保证安全(要修改必须先锁定),而只用一个线程来操作DOM的话就不用考虑同步问题了。

    备注:后面即使有支持了"多线程"——下文提到的Worker,也只有主线程能够操作DOM。

    3.事件循环什么?

     事件循环就是执行线程不断的从队列中取任务-处理任务-取任务的过程。事件循环运用在很多场景下,例如NIO模型,由一个线程负责多个socket的通信,节省线程资源。

    4.宏任务,微任务?

    JS运行模型有两种任务类型:

    宏任务(macrotask)=> 点击事件,定时器(setTimeout, setInterval),IO事件

    微任务(microtask)=> 常见的Promise

    相应的也有两个任务队列:宏任务队列和微任务队列

    5.执行线程只有一个,两个任务队列的任务执行顺序如何选择?

    每处理完一个宏任务,就会处理完微任务队列中的所有任务

    6.任务是谁生成的,谁负责插入队列?

    宏任务:

    • 页面按钮被点击时,浏览器线程(非JS主线程)生成一个onclick的回调任务插队列
    • 代码执行到setTimeout的时候,浏览器定时任务开始计时,等时间到的时候将任务插入队列
    • 浏览器线程收到http响应时,根据ajax提供的回调函数生成任务插入队列。

    备注:setTimeout, ajax等是浏览器提供的Web API,JS引擎并不包含这些内容,调用这些API会将任务信息告知浏览器,浏览器线程负责任务执行,执行完成时,根据提供的回调函数生成任务插入队列等待处理。

    微任务:

    • new 一个Promise对象时,会将Promise对象内部的操作包装成一个任务插入队列,然后立即返回(因为插入队列就完事了)。

    7.生成器(Generator)和执行器是什么东西,干啥用的?

    一个函数X内可能有多个操作,这几个操作要求有序,A->B->C,但又不希望主线程一直卡在X函数上(一调用X函数就一直等到他结束)。所以A,B,C可能通过嵌套Promise来实现(A的then函数执行B,B的then函数执行C),这样问题就解决了,即保证了异步(主线程不会卡在函数X上),又保证了有序(函数X内部操作有序)。但是如果函数X内有多个这样的操作,就会嵌套得很深,代码就会很冗长,看起来不舒服。所以就有了生成器和执行器,可以让函数X的结构更清晰一点。所以它的出现只是为了优化结构。

    //完全同步,主线程要按顺序全部执行完A,B,C才能处理其他事情
    function X() {
        A();
        B();
        C();  
    }
    
    X(); 
    //----------------------------------------------------------------------------
    
    //使用Promise异步化,处理完A,在处理B之前,如果有其他任务可以先处理其他任务。
    function X() {
        new Promise(() => {
            A();
        }).then(() => {
            new Promise(() => {
                B();
            }).then(() => {
                C();
            })
        })  
    }
    
    X();
    //----------------------------------------------------------------------------
    
    //生成器,生成器内代码结构清晰,A执行完执行B,B执行完执行C。不会有乱七八糟的嵌套
    function* generatorOfX() {
        //A执行完就返回,直到有人调用next()才会继续执行,下面同理。
        yield new Promise (() => A()); 
        yield new Promise (() => B());
        yield new Promise (() => C());
    }
    
    //执行器
    function run(gen) {
        var g = gen();
    
        function netxt(data) {
            var result = g.next(data);
            if(result.done) {
                return result.value;
            }
            if(result.value instanceof Promise) {
                result.value.then(function(data) {
                    next(data);
                })
            } else {
                next(data);
            }
        }
    
        next(); //开始执行生成器的第一步
    }
    
    function X() {
        run(generatorOfX);
    }
    
    X();
    //----------------------------------------------------------------------------

    联想:关于长作业与响应时间

    CPU对作业的控制,CPU有时间片机制,一个作业当前时间片用完即切换到其他任务,这样总的处理时间并不会减少,但是任务的“响应时间”(响应时间 = 任务开始执行的时间 - 任务提交的时间)变少了。

    8.async/await关键字是干啥用的?

    简单的说,这两个是上面生成器和执行器的语法糖。有了这个,我们就不用再去把一个函数写成生成器的样子,也不用自己实现执行器。

    //异步函数,内置执行器,不需要自行实现。
    async function X() {
        await new Promise(() => A());
        await new Promise(() => B());
        await new Promise(() => C());
    }
    
    X();

    9.await关键字会阻塞线程吗?

    不会,前面说了这个是生成器和执行器的语法糖。执行到await A()那一行代码的时候,只是将A函数加入任务队列,保存当前上下文,主线程跳出当前函数X继续执行,等到某个时候A函数被执行完成了,就会从该点恢复继续向下执行。所以await只是语义上的阻塞,并不会实际阻塞线程,它只是保证有序。

    10.Web Worker是什么?

    前面说了JS是单线程的,一个进程内只有一个线程。但是可以再开一个进程啊,这样多线程不就有了嘛。浏览器就提供这样的API,能够让用户代码通过new Worker的方式再开一个线程为用户服务。Worker与主线程通过事件进行交互。Worker线程无法访问DOM。

    注意:这个是运行环境提供的(浏览器有提供,NodeJS引入相关的库也可以支持),JS内核并不支持多线程。

    11.Web Worker的应用场景

    • 用一个Worker来轮询后端接口查询有无告警信息,有变更再通知主线程刷新UI(比较理想的是使用websocket,但是有些场景专门为了一个小功能引入websocket不合适,也有可能后端没空做)
    • 页面上某种类型的计算任务较多,主线程忙不过来,就会造成任务响应慢,这时候可以将此种类型任务抽取到Worker中进行处理

    12.什么时候会造成页面“很卡”?

    因为浏览器的执行模型,就是每隔一段时间刷新页面(每个固定时间将刷新任务插入队列),而这个刷新任务也是由主线程处理的。而渲染任务优先级较低,它在每个宏任务之前执行。也就是前面的宏任务或微任务占用了太多时间,页面就迟迟没有重新渲染,页面的刷新频率达不到60次/秒,肉眼感觉就会很卡,比如说,你在浏览页面,拉滚动条,发现页面不会滚或滚得很慢。

    13."响应慢"和"卡顿"不是一回事

    响应慢是指做了一个操作过了很久才成功,但是页面还能正常浏览。卡顿则是页面怎么操作都没有任何响应。比如同样刷新页面,前者页面显示转圈,转了很久才加载成功。后者是转圈直接转不动了。

    14."响应慢"和"卡顿"的优化方式
    用户体验到的响应慢可能有很多原因,也有相对应的应对方式

    • 后端处理慢 => 后端负责优化(缓存,任务拆分并行处理)
    • 资源加载慢 => 考虑客户端资源缓存,CDN
    • 客户端渲染慢,例如一个几万人的组织架构树  => 考虑增量刷新

    而卡顿的主要原因就是单个任务太耗时了,导致渲染任务的执行频率达不到。这时候就要对大任务进行拆分,异步化。例如一个函数包含A和B两个操作,可使用setTimeout,将一个宏任务拆成两个。这样执行顺序就可以变成 A->渲染->B。

    //todo 增加一些配图,更直观一点

    15.推荐阅读

  • 相关阅读:
    JVM
    关于filter
    session
    xml
    互联网应用和企业级项目的区别
    本学期javaee目标
    团队项目软件度量
    团队项目总结
    团队项目来换网最新
    ubuntu18.04 编译opencv4.4.0 带cuda加速,ffmpeg
  • 原文地址:https://www.cnblogs.com/longfurcat/p/13821198.html
Copyright © 2011-2022 走看看