zoukankan      html  css  js  c++  java
  • EventLoop

    文章资料来自
    Node.js 事件循环机制
    JS灵魂之问(下)

    EventLoop的中国名字叫事件循环,这个玩意真的是高深莫测,一般开发都用不到,代码只管写就行,虽然不用懂,但是面试就是要问,这对我这种小菜鸡真是满满的恶意

    先说说异步IO
    这个在Linux笔记里有,但是异步IO只有 Linux 下存在,在其他系统中没有异步 IO 支持,那window的异步IO是怎么实现的,利用多线程,我们可以让一个进程进行计算操作,另外一些进行 IO 调用,IO 完成后把信号传给计算的线程,进而执行回调,这不就好了吗?没错,异步 IO 就是使用这样的线程池来实现的,只不过在不同的系统下面表现会有所差异,在 Linux 下可以直接使用线程池来完成,在Window系统下则采用 IOCP 这个系统API(其内部还是用线程池完成的)

    上面的三个线程池都加粗了,因为他就是关键字,线程池的运行图很常见

    image.png

    V8、事件循环、事件队列都在单线程中运行,最右侧还有工作线程(Worker Thread)负责提供异步的I/O操作,这就是为什么说Node.js拥有非阻塞的,事件驱动的异步IO架构

    不仅是异步IO运行在线程池,NodeJS的计时器,http请求,浏览器的计时器,http请求ajax,ui渲染也都是运行在线程池的,也就是说js是单线程运行是错的,他是同步任务单线程运行,在【Linux/IO】笔记里把NodeJS比作餐厅是最简单的理解,他有个问题是菜做好了通知服务生来拿,IO执行完是不会通知服务生来拿的,正在的通知是线程池里的线程做的,也就是说服务生拿了菜单到厨房后,放了一招【影分身之术】,叫了一个线程在门口等着【上图的观察者】,菜做好了影分身喊了一句菜做好了,然后自己就消失了,这是主线程服务生才知道才做好了

    原理代码

    /**
     * 定义事件队列
     * 入队:push()
     * 出队:shift()
     * 空队列:length == 0
     */
    var globalEventQueue = []
    
    /**
     * 接收用户请求
     * 每一个请求都会进入到该函数
     * 传递参数request和response
     */
    function processHttpRequest(request,response){
         
        // 定义一个事件对象
        var event = createEvent({
            params:request.params, // 传递请求参数
            result:null, // 存放请求结果
            callback:function(){} // 指定回调函数
        });
     
        // 在队列的尾部添加该事件  
        globalEventQueue.push(event);
    }
    
    /**
     * 事件循环主体,主线程择机执行
     * 循环遍历事件队列
     * 处理非IO任务
     * 处理IO任务
     * 执行回调,返回给上层
     */
    function eventLoop(){
        // 如果队列不为空,就继续循环
        while(this.globalEventQueue.length > 0){
             
            // 从队列的头部拿出一个事件
            var event = this.globalEventQueue.shift();
             
            // 如果是耗时任务
            if(isIOTask(event)){
                // 从线程池里拿出一个线程
                var thread = getThreadFromThreadPool();
                // 交给线程处理
                thread.handleIOTask(event)
            }else {
                // 非耗时任务处理后,直接返回结果
                var result = handleEvent(event);
                // 最终通过回调函数返回给V8,再由V8返回给应用程序
                event.callback.call(null,result);
            }
        }
    }
    
    /**
     * 处理IO任务
     * 完成后将事件添加到队列尾部
     * 释放线程
     */
    function handleIOTask(event){
        //当前线程
        var curThread = this;
     
        // 操作数据库
        var optDatabase = function(params,callback){
            var result = readDataFromDb(params);
            callback.call(null,result)
        };
         
        // 执行IO任务
        optDatabase(event.params,function(result){
            // 返回结果存入事件对象中
            event.result = result;
     
            // IO完成后,将不再是耗时任务
            event.isIOTask = false;
             
            // 将该事件重新添加到队列的尾部
            this.globalEventQueue.push(event);
             
            // 释放当前线程
            releaseThread(curThread)
        })
    }
    

    MicroTask
    这个词的中国名字叫微任务,这个概念是跟着Promise一起出现的,百度Promise都会提到他解决了回调地狱

    // 之前
    fs.readFile('1.json', (err, data) => {
        fs.readFile('2.json', (err, data) => {
            fs.readFile('3.json', (err, data) => {
                fs.readFile('4.json', (err, data) => {
    
                });
            });
        });
    });
    
    // 现在
    readFilePromise('1.json').then(data => {
        return readFilePromise('2.json')
    }).then(data => {
        return readFilePromise('3.json')
    }).then(data => {
        return readFilePromise('4.json')
    });
    

    Promise确实是解决了回调地狱,但这只是改变了写法,在没有Promise的时代,代码也一样运行,那Promise到底带来了什么,微任务带来了什么,带来了宏任务,233333,上面的EventLoop就是宏任务的运行规则,在没有微任务的时候就是这么循环执行的,但是看上面的模拟运行,异步回调被放在了执行栈数组的最后面,倘若现在的任务队列非常长,那么回调迟迟得不到执行,造成应用卡顿,于是他们开辟了微任务队列,也就是第二个数组

    1. 一开始整段脚本作为第一个宏任务执行
    2. 执行过程中同步代码直接执行,宏任务进入宏任务队列,微任务进入微任务队列
    3. 当前宏任务执行完出队,检查微任务队列,如果有则依次执行,直到微任务队列为空
    4. 执行浏览器 UI 线程的渲染工作
    5. 检查是否有Web worker任务,有则执行
    6. 执行队首新的宏任务,回到2,依此循环,直到宏任务和微任务队列都为空

    放到微任务队列怎么理解呢
    把下面的代码运行一下,再把注释解开运行一下
    正常来说第一次是 1 2 3 4 2.1,因为400ms的计数器,等他回到微任务队列,0ms的计数器都执行完了
    正常来说第二次是 1 2 3 ... 2.1 4,当0ms的定时器返回,循环还在继续,循环快完的时候,400ms的定时器也返回了,这时4是在2.1之前的,但是还是2.1比4先输出,因为他插队了,在微任务队列了实现了插队

    console.log(1)
    new Promise(function(x,y){
       console.log(2)
       setTimeout(()=>{
    	console.log(2.1)
       },400)
    }).then(x=>{
       console.log(x)
    })
    console.log(3)
    // for(var i=3;i<10000;i++){
       // console.log(i)
    // }
    setTimeout(()=>{
       console.log(4)
    })
    

    在浏览器是上面这么执行的,而NodeJS还在循环结束加了个nextTick函数,这是必须在微任务执行队列执行完后执行的,也就是第三个数组,Vue也有一个nextTick是在异步的更新dom后执行的,模仿nodejs的执行概念

    就这个理解面试应该没问题了吧,广州有没有招人的,年后想换工作,求收留

  • 相关阅读:
    abstract关键字
    final关键字
    Vue使用枚举类型实现HTML下拉框
    第八节 pandas读取和保存文件
    第七节 pandas新建数据框的两种方式
    第六节 numpy的常用属性和方法
    第五节 numpy的简单使用
    第三节 matplotlib绘制直方图
    第三节 matplotlib绘制条形图
    第二节 matplotlib绘制散点图
  • 原文地址:https://www.cnblogs.com/pengdt/p/12240497.html
Copyright © 2011-2022 走看看