事件循环和异步IO

242 阅读4分钟

coderwwh w:what是什么? w:why为什么? h:how怎么样?

事件循环是什么?

是:JS代码和浏览器(或node)之间的桥梁

参考coderwhy老师的图

图片.png

知识补充:进程和线程

是什么

  • 进程:计算机已经运行的程序
  • 线程:操作系统能够运行调度的最小单元
  • 进程:我们可以认为,启动一个应用程序,就会默认启动一个进程(也可能是多个进程);
  • 线程:每一个进程中,都会启动一个线程用来执行程序中的代码,这个线程被称之为主线程;
  • 所以我们也可以说进程是线程的容器

多进程多线程开发

浏览器和js

我们知道js是单线程的,但是 Javascript 的线程应该有自己的容器进程:浏览器或者 Node

浏览器是多进程的,一个tab是一个进程,防止一个卡死

每个进程又有很多线程,其中包括js线程

但是js是单线程的,意味着

  • js在同时只能做同一件事
  • 如果这件事十分耗时,当前线程会被阻塞

浏览器的事件循环

图片.png

每一个回调函数就是一个任务,是宏任务,加到宏任务任务队列里

  • ajax
  • settimeout
  • dom监听
  • 。。。

promise.then产生的回调加入微任务任务队列(还有其他的)

  • promise.then
  • mutation Observer API
  • queueMicro
  • 。。。

微任务 > 宏任务

执行完一个宏任务队列后,不会再下一个宏任务,会判断在该宏任务执行过程中有没有新微任务产生,如果有,先执行微任务,再执行下一个宏任务

测试

图片.png

图片.png

阻塞IO与非阻塞IO

思考:JavaScript可以直接对一个文件进行操作吗?

看起来是可以的,但是事实上我们任何程序中的文件操作都是需要进行系统调用(操作系统的文件系统)

事实上对文件的操作,是一个操作系统的系统调用(IO系统是输入、输出);

操作系统通常为我们提供了两种调用方式:

阻塞式调用和非阻塞式调用:

  • 阻塞式调用:调用结果返回之前,当前线程处于阻塞态(阻塞态CPU是不会分配时间片的),调用线程只有在得到调用结果之后才会继续执行。

  • 非阻塞式调用:调用执行之后,当前线程不会停止执行,只需要过一段时间来检查一下有没有结果返回即可。

非阻塞IO问题

非阻塞IO也会存在一定的问题:我们并没有获取到需要读取(我们以读取为例)的结果

意味着为了可以知道是否读取到了完整的数据,我们需要频繁的去确定读取到的数据是否是完整的;

这个过程我们称之为轮训操作;


谁来操作

这个轮训的工作由谁来完成呢?

如果我们的主线程频繁的去进行轮训的工作,那么必然会大大降低性能;

并且开发中我们可能不只是一个文件的读写,可能是多个文件;

而且可能是多个功能:网络的、数据库的、子进程调用;


libuv提供了一个线程池(Thread Pool):

  线程池会负责所有相关的操作,并且会通过轮训或者其他的方式等待结果
   当获取到结果时,就可以将对应的回调放到事件循环(某一个事件队列)中
  事件循环就可以负责接管后续的回调工作,告知JavaScript应用程序执行对应的回调函数

阻塞和非阻塞)区别(同步和异步)

阻塞和非阻塞是对于被调用者来说的

  在我们这里就是系统调用,操作系统为我们提供了阻塞调用和非阻塞调用
  

同步和异步是对于调用者来说的

  在我们这里就是自己的程序
  如果我们在发起调用之后,不会进行其他任何的操作,只是等待结果,这个过程就称之为同步调用
 如果我们再发起调用之后,并不会等待结果,继续完成其他的工作,等到有回调时再去执行,这个过程就是异步调用

node里的事件循环

stream

一连串的字节

在之前学习文件的读写时,我们可以直接通过readFile或者writeFile方式读写文件,为什么还需要流呢?

   直接读写文件的方式,虽然简单,但是无法控制一些细节的操作;
   比如从什么位置开始读、读到什么位置、一次性读取多少个字节;
   读到某个位置后,暂停读取,某个时刻恢复读取等等;
   或者这个文件非常大,比如一个视频文件,一次性全部读取并不合适;

文件读写的stream

//流的方式读取
const reader = fs.createReadStream("./foo.txt", {
    start: 3,
    end: 6,
    highWaterMark: 2
))
reader.on("data", (data) => {
    console.log(data);
})