V8引擎详解(九)——协程&生成器函数

3,955 阅读6分钟

前言

本文是V8引擎详解系列的第九篇,重点内容是关于生成器函数的运作机制,以及协程的概念。文末会有已经完成的系列文章的链接,本系列文章还在不断更新欢迎持续关注。

生成器函数Generator

Javascript最初的规则中中,一个函数开始执行后,就会运行到最后或遇到return时结束,运行期间不会有其它代码能够打断它。而ES6中引入的Generator(生成器)函数打破了这个规则,Generator函数可以交出函数的执行权(暂停执行),这也是和普通函数最大的区别。
先说一下Generator的几个特点:

  • 调用 Generator 函数,会返回一个内部指针(遍历器Iterator对象
  • 通过调用 遍历器Iterator对象 的next方法,遍历 Generator函数内部的每一个状态
  • 创建时通过 在function关键字与函数名添加 "*" 用以和普通函数做区分
  • 函数体内通过多个 yield 表达式,定义不同的内部状态,遍历器对象 使用next遍历时即返回当前的 yield 状态

通过一小段代码来看一下Generator函数

function* gen() {
    yield 'first';
    yield 'second';
    yield 1 + 2;
    return 'end';
}

let g = gen();
g.next(); // {value: 'first', done: 'false'};
g.next(); // {value: 'second', done: 'false'};
g.next(); // {value: '3', done: 'false'};
g.next(); // {value: 'end', done: 'true'};

用起来也很简单,通过构建函数 yiled 构建阶段状态,调用时通过调用next返回当前状态的信息(value)以及执行状态(done)。也正是通过Generator函数和Promise的结合彻底解决了回调地狱的问题,并于ES7规范推出了官方语法糖async/await
本文主要来聊聊Generator函数到底是什么。

进程与线程

想要了解协程就要先了解进程与线程的概念(这几个概念在计算机中都是属于比较抽象的概念也从来没有统一的标准定义,所以下面只代表个人的观点与理解)。

进程

  • 计算机的核心CPU承担所有的计算任务(先不说GPU)。
  • 操作系统管理计算机,负责任务的调度,资源的分配和管理。
  • 程序作用于操作系统,用来执行某种功能的集合。
  • 进程是为了实现程序的某种功能,对于某个数据集合的一次动态执行的过程。
  • 进程是操作系统进行资源分配和调度的一个独立单位(实体),也是操作系统执行的基本单元,程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程

线程

进程最基本的能力就是 被操作系统调度 以及 进行资源分配和调度,是一个调度资源的基本单位。
作为一个调度资源的基本单位希望可以同一时间干多件事情,于是诞生了 线程 的概念。

线程通过以下特点来帮助进程可以在同一时间完成多件事情:

  • 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位
  • 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线
  • 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见
  • 线程上下文切换比进程上下文切换要快得多

以上便是进程和线程的基本概念,如果觉得我说的还是比较抽象,可以参考一下阮大大的进程与线程的一个简单解释来帮助理解。

协程和生成器函数

Generator函数 本质上就是协程的一种实现,如果你能理解协程是什么那么你就能轻松的理解Generator函数

协程到底是什么

协程是一种比线程更加轻量级的存在。你可以把协程看成是跑在线程上的任务,一个线程上可以存在多个协程,但是在线程上同时只能执行一个协程。

协程概念的提出比较早,单核CPU场景中发展出来的概念,通过提供挂起和恢复接口,实现在单个CPU上交叉处理多个任务的并发功能。
那么本质上就是在一个线程的基础上,增加了不同任务栈的切换,通过不同任务栈的挂起和恢复,线程中进行交替运行的代码片段,实现并发的功能。(注意是并发不是并行)

协程的优势

  • 避免锁竞争

多个线程之间调用资源时会发生竞争关系,会导致锁的竞争,因为协程本身就是单核cpu上进行操作(在一个线程上同时只能执行一个协程),所以不存在竞争关系。

  • 协程消耗资源远比线程小

协程的每个任务栈的占用的内存空间远比线程小,在高并发场景,线程太多也会导致OOM

  • 切换成本极低

协程的切换(挂起和恢复)完全由用户控制,不需要系统进行切换,成本极低。

javascript中的协程

ES6中引入的生成器函数,就是给用户提供了协程特性的入口,通过生成器函数让我们可以使用协程的特性。
这些特性使得程序更适合高并发的I/O操作,更容易的真正解决了回调地狱的问题。

总结

本文主要了解了关于生成器函数的运作机制,以及协程的概念,如果你之前觉得不够理解生成器函数,读完本文会发现其实本质上生成器函数不难理解,只要可以理解协程的概念,也就能明白为什么会出现生成器函数。如果有什么错误,请在评论中和作者一起讨论,如果您觉得本文对您有帮助请帮忙点个赞,感激不尽。

系列文章

V8引擎详解(一)——概述
V8引擎详解(二)——AST
V8引擎详解(三)——从字节码看V8的演变
V8引擎详解(四)——字节码是如何执行的
V8引擎详解(五)——内联缓存
V8引擎详解(六)——内存结构
V8引擎详解(七)——垃圾回收机制
V8引擎详解(八)——消息队列
V8引擎详解(九)——协程&生成器函数