day16 为什么环形队列适合做Node数据流缓存?

如何实现队列和双队列
数据结构中队列的核心思想和我们排队买票看电影一样,关键是谁排在前面,谁就可以先买到票。入队(enqeue),顾名思义就是在队伍后面加了一个人排队。按照先入先出的规则,排在最前面的人买完票了以后,就会出队(dequeue)。
双队列(deque)
通常我们排队的时候,都是遵循先进先出的规则,但是在有些特殊的情况下,也会有特例。在 JavaScript 中呢,同样有一个 unshift() 的方法可以用来做到插队,我们在下面例子里把它叫做 dequeAdd。还有另外一种情况就是如果有的人在队尾等不及了,也有可能离开,这样的话我们可以借用弹出栈的 pop() 来实现,我们在下面例子里把它叫做 dequeRemove

class Queue {
  constructor () {
    this.queue = [];
  }
  enqueue(item) {
    return this.queue.push(item);
  }
  dequeue() {
    return this.queue.shift();
  }
  dequeAdd(item) {
    return this.queue.unshift(item);
  }
  dequeRemove(item) {
    return this.queue.pop(item);
  }
  peek() {
    return console.log(this.queue[0]);
  }
}

通过队列看浏览器任务管理
进程(process)和线程(thread)
Chromium 用的是多进程的架构(multi-process architecture)。一个新的标签页就是新的浏览器进程。
在一个浏览器进程里,会有多个线程。
Chromium 中有两个核心线程,一个是主线程,另外一个是 IO 线程。因为浏览器是面向前端用户的,所以对于它的架构设计来说,最主要的目标是让主线程和 IO 线程可以快速响应。为了达到这个目的,就要把阻塞 IO 或复杂运算分给其它的线程去处理,线程间通信问题通过消息传递来解决。
可以说 Chromium 用的是一个高并发,但不算是高并行的架构。对于页面加载的脚本中要执行的任务,会采用任务队列的方式通过事件循环给到 UI 主线程。如果问题是主线程可以解决的,就会处理,如果处理不了的,就会给到 IO 线程或特殊线程来处理,处理的结果会通过消息通信的方式给到主线程。
线程池有一个共享的任务队列
在 Chromium 线程管理当中,并不提倡用锁的结构,而是提倡序列或虚拟线程管理的概念。因为在 Google 看来,序列本身就带有线程安全性。
因为在虚拟线程管理中,只有当一个任务执行完,下一个任务才有可能被分配到线程池中的另一个工作线程来执行,所以下一个任务肯定是基于上一个任务的结果来执行的。任务的执行具有前后顺序。

这里我们可以打一个比方,比如我们在项目管理中,会有一个需求池,这个需求池就是我们的任务队列,而虚拟线程管理就如同一个项目经理。
在 Chromium 中,通常当有多个线程(worker thread)可以访问一个数据资源,但是同一时间只允许一个线程来访问这些资源或数据的时候,会用到锁。在 Chromium 当中使用的是互斥锁(mutex)。
环形队列和线程间数据传递
环形队列是一种特殊的队列。一个环形队列是首尾相连的。它也叫做环形缓冲区(ring buffer),可以用于进程或线程之间的数据传递。
为什么环形队列适合做 Node 数据流缓存?最核心的好处是当一个数据元素被用掉后,其余数据元素不需要移动其存储位置。
对于一个环形队列来说,我们一般需要两个指针,一个是头指针,一个是尾指针。头指针指向的是下一个要加入的数据,尾指针指向下一个要读取的数据。当数据被读取时,我们就增加尾指针;当数据被写入的时候,我们就增加头指针。
举个例子,假设我们有一个 16 位的缓冲,第一步,我们加入了 4 位数据,头指针就移动到了 3。如果再加 3 个的话,头指针就移动到了 6。如果这时,我们读取了前 4 个,那么尾指针就会到 4。

那么在程序中这种环形队列如何实现呢?从实现的角度,一般会建立两个数组,一个是原数组用来定义环形队列的属性和方法,第二个是实际用来存放数据的环形队列数组。在原数组里面,主要存放 3 个关键属性,分别是头指针、尾指针和环形队列长度。同时,包含几个核心方法:原数组中属性的获取和设置,以及环形队列数组中数据的读和写。通过这种方式,就可以实现一个环形队列了。
下面我们可以来看看它在缓存数据流中的应用。这种环形队列通常会和“生产者,消费者”模式一起用,也通常会加一个互斥锁到环形队列的读、写方法里,用来实现互斥访问。如下图所示,我们可以看到有 4 个工作线程。2 个是生产者,2 个是消费者。 生产者负责写入数据,消费者读取数据。通过加锁的方式,在同一时间,只有一个生产者可以写入,在读的时候,也只有一个消费者可以读取。
day16 为什么环形队列适合做Node数据流缓存?_第1张图片
在数据流这种大量数据持续进入到列队中,再从队列中取出做缓存处理的情况下,使用环形队列就大大增加了生产者和消费者之间协同合作的效率。
数据流缓冲在很多应用中都有体现,除了进程管理外,在网络和文件系统中,都会用到数据流缓冲。在网络中,字节数据都是按照包的大小分成块儿来传输的;在文件系统中,数据块儿都是根据内核的缓冲大小分成块儿来处理的。所以无论是 HTTP 的数据请求和反馈,还是文件到目的地的传输,这些数据的传输都会用到环形队列缓冲。

你可能感兴趣的:(前端javascript)