Support Statistics
¥.00 ·
0times
Text Preview (First 20 pages)
Registered users can read the full content for free
Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.
Page
1
(This page has no text content)
Page
2
版权信息 书名:Go语言底层原理剖析 作者:郑建勋 排版:辛萌哒 出版社:电子工业出版社 出版时间:2021-08-01 ISBN:9787121416620 本书由电子工业出版社有限公司授权北京当当科文电子商务有限公司制作与发行。 — · 版权所有 侵权必究 · —
Page
3
(This page has no text content)
Page
4
内容简介 Go语言是一门年轻、简捷,但是强大、高效、充满潜力的服务器 语言。本书使用浅显易懂的语言与大量图片深入介绍了Go语言编译 时、类型系统、语法特性、函数调用规范、并发编程、内存管理与垃 圾回收、运行状态监控等模块的使用方法和底层原理,并举例说明了 构建大型软件工程需要遵循的设计规则,让读者系统并完整掌握Go语 言的底层细节。 本书适合有一定工作经验的开发工程师阅读,以便进一步提升自 己,更好地开发软件、系统架构,并参加工作面试。本书也可以作为 高等院校计算机专业师生学习编程语言设计原理的参考教材。
Page
5
前言 Go语言虽然是一门非常年轻的语言(2009年正式开源),却以不 可思议的速度在成长着。 顶级大公司(谷歌)的支持、顶尖的设计者(罗勃·派克、肯· 汤普逊)和豪华的开发团队、“杀手级”的项目(Kubernetes)、开放 活跃的社区以及数以百万计的开发者都揭示了Go语言的巨大潜力。在 国内,Go语言良好的发展趋势可以从招聘网站中数量庞大的岗位需求 以及每天发表在各种媒体上的种类繁多的相关文章中得到印证。 在可预见的未来,相信Go语言还将延续强劲的发展势头。为了把 握和适应时代的需求,开发者需要在短时间内掌握Go这门语言。虽然 高级语言足够抽象,在大部分情况下掌握了基本的语法就可以创建庞 大和复杂的项目,但是仍然需要遵守一定的规则才能写出正确、优 雅、容易维护的代码。当程序的执行结果不符合预期时,我们是否有 足够多的手段去调试?很显然,会使用和能用好有本质的区别,充分 的经验、合理的架构、遵守软件设计规范、使用经典的设计模式、规 避常见的错误陷阱、强制性工具检查都能够帮助开发者写出更好的程 序。然而很多时候我们并不满足于此,还希望探究语言背后的原理。 Go语言的编译器、运行时,本身就是用Go语言写出的既复杂又 精巧的程序;探究语言设计、语法特性,本身就是学习程序设计与架 构、数据结构与算法等知识的绝佳途径。学习底层原理能够帮助我们 更好地了解Go语言的语法,做出合理的性能优化,设计科学的程序架 构,监控程序的运行状态,排查复杂的程序异常问题,开发出检查协 程泄露、语法等问题的高级工具,理解Go语言的局限性,从而在不同
Page
6
场景下做出合理抉择。学习Go语言底层原理能提升自己的专业技能和 薪资水平,这种学习本身也是一种乐趣,而这种乐趣恰恰是很多自上 而下学习编程语言的开发者不能体会的。 目前市面上鲜有系统介绍Go语言底层实现原理的书籍,为了弥补 这个缺陷,笔者写作本书,系统性地介绍Go语言在编译时、运行时以 及语法特性等层面的底层原理和更好的使用方法。本书由21章组成, 这21章可以分为6部分。 第1~8章为第1部分,介绍Go语言的基础——编译时及类型系 统。包括浮点数、切片、哈希表等类型以及类型转换的原理。 第9~11章为第2部分,介绍程序运行重要的组成部分——函数与 栈。包括栈帧布局、栈扩容、栈调试的原理,并介绍了延迟调用、异 常与异常捕获的原理。 第12、13章为第3部分,介绍Go语言程序设计的关键——接口。 包括如何正确合理地使用接口构建程序、接口的实现原理和可能遇到 的问题,并探讨了接口之上的反射原理。 第14~17章为第4部分,介绍Go语言并发的核心——协程与通 道。详细论述了协程的本质以及运行时调度器的调度时机与策略。介 绍了通过通信来共享内存的通道本质以及通道的多路复用原理,并探 讨了并发控制、数据争用问题的解决办法及锁的本质。 第18~20章为第5部分,介绍Go语言运行时最复杂的模块——内 存管理与垃圾回收。详细论述了Go语言中实现内存管理方法及垃圾回 收的详细步骤。 第21章为第6部分,介绍Go语言可视化工具——pprof与trace。详 细论述了通过工具排查问题、观察系统运行状态的方法与实现原理。 为了准确地论述每一个话题,笔者参阅了市面上能够找到的文 章、提案并详细参考了相应的源代码。从某种意义上来讲,这是一本 站在巨人肩膀上的著作。学习原理是为了更好地使用,笔者在本书中
Page
7
不会粘贴大段的源码,而是将其充分整理并结合了作者的思考后用图 和例子的形式来讲解。相信当读者阅读完本书后能够建立一整套底层 原理模型并对程序有不一样的体会,就像清晰地看到了Go程序中的每 一根血管和每一个细胞一样。 本书各章参考资料可通过微信扫描封底二维码获取。 郑建勋 2021年5月
Page
8
第1章 深入Go语言编译器 以.go为后缀的UTF-8格式的Go文本文件最终能被编译成特定机器 上的可执行文件,离不开Go语言编译器的复杂工作。Go语言编译器不 仅能准确地翻译高级语言,也能进行代码优化。在本章中,笔者将解 析从编写Go文本文件到生成可执行文件的关键流程。 1.1 为什么要了解Go语言编译器 编译器是一个大型且复杂的系统,一个好的编译器会很好地结合 形式语言理论、算法、人工智能、系统设计、计算机体系结构及编程 语言理论。Go语言的编译器遵循了主流编译器采用的经典策略及相似 的处理流程和优化规则(例如经典的递归下降的语法解析、抽象语法 树的构建)。另外,Go语言编译器有一些特殊的设计,例如内存的逃 逸等。在本章中,笔者将分别介绍Go语言编译器的各个阶段。 编译原理值得用一本书的笔墨去讲解,通过了解Go语言编辑器, 不仅可以了解大部分高级语言编译器的一般性流程与规则,也能指导 我们写出更加优秀的程序。本书后面的章节经常会禁用编译器的优化 以及内联函数等特性调试和查看代码的执行流程。后面还会看到,很 多Go语言的语法特性都离不开编译时与运行时的共同作用。另外,如 果读者希望开发go import、go fmt、go lint等扫描源代码的工具, 那么同样离不开编译器的知识和Go语言提供的API。 1.2 Go语言编译器的阶段 如图1-1所示,在经典的编译原理中,一般将编译器分为编译器前 端、优化器和编译器后端。这种编译器被称为三阶段编译器(three- phase compiler)。其中,编译器前端主要专注于理解源程序、扫描
Page
9
解析源程序并进行精准的语义表达。编译器的中间阶段 (Intermediate Representation,IR)可能有多个,编译器会使用多 个IR阶段、多种数据结构表示代码,并在中间阶段对代码进行多次优 化。例如,识别冗余代码、识别内存逃逸等。编译器的中间阶段离不 开编译器前端记录的细节。编译器后端专注于生成特定目标机器上的 程序,这种程序可能是可执行文件,也可能是需要进一步处理的中间 形态obj文件、汇编语言等。 图1-1 三阶段编译器 需要注意的是,编译器优化并不是一个非常明确的概念。优化的 主要目的一般是降低程序资源的消耗,比较常见的是降低内存与CPU的 使用率。但在很多时候,这些目标可能是相互冲突的,对一个目标的 优化可能降低另一个目标的效率。同时,理论已经表明有一些代码优 化存在着NP难题[1],这意味着随着代码的增加,优化的难度将越来越 大,需要花费的时间呈指数增长。因为这些原因,编译器无法进行最 佳的优化,所以通常采用一种折中的方案。 Go语言编译器一般缩写为小写的gc(go compiler),需要和大写 的GC(垃圾回收)进行区分。Go语言编译器的执行流程可细化为多个 阶段,包括词法解析、语法解析、抽象语法树构建、类型检查、变量 捕获、函数内联、逃逸分析、闭包重写、遍历函数、SSA生成、机器码 生成,如图1-2所示。后面的章节将对这些阶段逐一进行分析。
Page
10
图1-2 Go语言编译器执行流程 1.3 词法解析 和Go语言编译器有关的代码主要位于src/cmd/compile/internal 目录下,在后面分析中给出的文件路径均默认位于该目录中。在词法 解析阶段,Go语言编译器会扫描输入的Go源文件,并将其符号 (token)化。例如“+”和“-”操作符会被转换为_IncOp,赋值符号 “:=”会被转换为_Define。这些token实质上是用iota声明的整数, 定义在syntax/tokens.go中。符号化保留了Go语言中定义的符号,可 以识别出错误的拼写。同时,字符串被转换为整数后,在后续的阶段 中能够被更加高效地处理。图1-3为一个示例,展现了将表达式a: =b+c(12)符号化之后的情形。代码中声明的标识符、关键字、运算 符和分隔符等字符串都可以转化为对应的符号。
Page
11
图1-3 Go语言编译器词法解析示例 Go语言标准库go/scanner、go/token也提供了许多接口用于扫描 源代码。在下例中,我们将使用这些接口模拟对Go文本文件的扫描。
Page
12
(This page has no text content)
Page
13
在上例中,src为进行词法扫描的表达式,可以将其模拟为一个文 件并调用scanner.Scanner词法,扫描后分别打印出token的位置、符 号及其字符串字面量。每个标识符与运算符都被特定的token代替,例 如2i被识别为复数IMAG,注释被识别为COMMENT。
Page
14
1.4 语法解析 词法解析阶段结束后,需要根据Go语言中指定的语法对符号化后 的Go文件进行解析。Go语言采用了标准的自上而下的递归下降(Top- Down Recursive-Descent)算法,以简单高效的方式完成无须回溯的 语法扫描,核心算法位于syntax/nodes.go及syntax/parser.go中。图 1-4为Go语言编译器对文件进行语法解析的示意图。在一个Go源文件中 主要有包导入声明(import)、静态常量(const)、类型声明 (type)、变量声明(var)及函数声明。
Page
15
图1-4 Go语言编译器对文件进行语法解析的示意图 源文件中的每一种声明都有对应的语法,递归下降通过识别初始 的标识符,例如_const,采用对应的语法进行解析。这种方式能够较
Page
16
快地解析并识别可能出现的语法错误。每一种声明语法在Go语言规范 中都有定义[2]。 函数声明是文件中最复杂的一类语法,因为在函数体的内部可能 有多种声明、赋值(例如:=)、表达式及函数调用等。例如defer语 法为defer Expression,其后必须跟一个函数或方法。每一种声明语 法或者表达式都有对应的结构体,例如a:=b+f(89)对应的结构体为 赋值声明AssignStmt。Op代表当前的操作符,即“:=”,Lhs与Rhs分 别代表左右两个表达式。
Page
17
语法解析丢弃了一些不重要的标识符,例如括号“(”,并将语 义存储到了对应的结构体中。语法声明的结构体拥有对应的层次结 构,这是构建抽象语法树的基础。图1-5为a:=b+c(12)语句被语法 解析后转换为对应的syntax.AssignStmt结构体之后的情形。最顶层的 Op操作符为token.Def(:=)。Lhs表达式类型为标识符 syntax.Name,值为标识符“a”。Rhs表达式为syntax.Operator加法 运算。加法运算左边为标识符“b”,右边为函数调用表达式,类型为 CallExpr。其中,函数名c的类型为syntax.Name,参数为常量类型 syntax.BasicLit,代表数字12。
Page
18
图1-5 特定表达式的语法解析示例 1.5 抽象语法树构建
Page
19
编译器前端必须构建程序的中间表示形式,以便在编译器中间阶 段及后端使用。抽象语法树(Abstract Syntax Tree,AST)是一种常 见的树状结构的中间态。 在Go语言源文件中的任何一种import、type、const、func声明都 是一个根节点,在根节点下包含当前声明的子节点。如下decls函数将 源文件中的所有声明语句转换为节点(Node)数组。核心逻辑位于 gc/noder.go中。
Page
20
每个节点都包含了当前节点属性的Op字段,定义在gc/syntax.go 中,以O开头。与词法解析阶段中的token相同的是,Op字段也是一个 整数。不同的是,每个Op字段都包含了语义信息。例如,当一个节点 的Op操作为OAS时,该节点代表的语义为Left:=Right,而当节点的操 作为OAS2时,代表的语义为x,y,z=a,b,c。
The above is a preview of the first 20 pages. Register to read the complete e-book.
Comments 0
Loading comments...
Reply to Comment
Edit Comment