搬砖民工也会建成自己的“罗马帝国”:Go语言创世纪

2019年6月23日 294点热度 0人点赞 0条评论

图片

来源 | 异步 | 文末赠书


图片

我不知道,你过去10年为什么不快乐。但相信我,抛掉过去的沉重,使用Go语言,体会最初的快乐!


——469856321

Go语言最初由谷歌公司的Robert Griesemer、Ken Thompson和Rob Pike这3位技术大咖于2007年开始设计发明,设计新语言的最初动力来自对超级复杂的C++11特性的吹捧报告的鄙视,最终的目标是设计网络和多核时代的C语言。到2008年中期,在语言的大部分特性设计已经完成并开始着手实现编译器和运行时,Russ Cox作为主力开发者加入。到2010年,Go语言已经逐步趋于稳定,并在9月正式发布并开源了代码。

Go语言很多时候被描述为“类C语言”,或者“21世纪的C语言”。从各种角度看,Go语言确实是从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等诸多编程思想,并彻底继承和发扬了C语言简单直接的暴力编程哲学等。图1-1给出的是The Go Programming Language中给出的Go语言的基因图谱,我们可以从中看到有哪些编程语言对Go语言产生了影响。

图片

图1-1 Go语言基因图谱

首先看基因图谱的左边一支。可以明确看出Go语言的并发特性是由贝尔实验室的Hoare于1978年发布的CSP理论演化而来。其后,CSP并发模型在Squeak/Newsqueak和Alef等编程语言中逐步完善并走向实际应用,最终这些设计经验被消化并吸收到了Go语言中。业界比较熟悉的Erlang编程语言的并发编程模型也是CSP理论的另一种实现。

再看基因图谱的中间一支。中间一支主要包含了Go语言中面向对象和包特性的演化历程。Go语言中包和接口以及面向对象等特性则继承自Niklaus Wirth所设计的Pascal语言以及其后衍生的相关编程语言。其中包的概念、包的导入和声明等语法主要来自Modula-2编程语言,面向对象特性所提供的方法的声明语法等则来自Oberon编程语言。最终Go语言演化出了自己特有的支持鸭子面向对象模型的隐式接口等诸多特性。

最后是基因图谱的右边一支,这是对C语言的致敬。Go语言是对C语言最彻底的一次扬弃,不仅在语法上和C语言有着很多差异,最重要的是舍弃了C语言中灵活但是危险的指针运算。而且,Go语言还重新设计了C语言中部分不太合理运算符的优先级,并在很多细微的地方都做了必要的打磨和改变。当然,C语言中少即是多、简单直接的暴力编程哲学则被Go语言更彻底地发扬光大了(Go语言居然只有25个关键字,语言规范还不到50页)

Go语言的其他特性零散地来自其他一些编程语言,例如,iota语法是从APL语言借鉴的,词法作用域与嵌套函数等特性来自Scheme语言(和其他很多编程语言)。Go语言中也有很多自己发明创新的设计。例如Go语言的切片为轻量级动态数组提供了有效的随机存取的性能,这可能会让人联想到链表的底层的共享机制。还有Go语言新发明的defer语句(Ken发明)也是神来之笔。

图片

点击封面 直达购书

 来自贝尔实验室特有基因

作为Go语言标志性的并发编程特性则来自贝尔实验室的Tony Hoare于1978年发表的鲜为外界所知的关于并发研究的基础文献:顺序通信进程(Communicating Sequential Processes,CSP)。在最初的CSP论文中,程序只是一组没有中间共享状态的并发运行的处理过程,它们之间使用通道进行通信和控制同步。Tony Hoare的CSP并发模型只是一个用于描述并发性基本概念的描述语言,它并不是一个可以编写可执行程序的通用编程语言。

CSP并发模型最经典的实际应用是来自爱立信公司发明的Erlang编程语言。不过在Erlang将CSP理论作为并发编程模型的同时,同样来自贝尔实验室的Rob Pike以及其同事也在不断尝试将CSP并发模型引入当时的新发明的编程语言中。他们第一次尝试引入CSP并发特性的编程语言叫Squeak(老鼠的叫声),是一个用于提供鼠标和键盘事件处理的编程语言,在这个语言中通道是静态创建的。然后是改进版的Newsqueak语言(新版老鼠的叫声),新提供了类似C语言语句和表达式的语法,还有类似Pascal语言的推导语法。Newsqueak是一个带垃圾回收机制的纯函数式语言,它再次针对键盘、鼠标和窗口事件管理。但是在Newsqueak语言中通道已经是动态创建的,通道属于第一类值,可以保存到变量中。然后是Alef编程语言(Alef也是C语言之父Ritchie比较喜爱的编程语言),Alef语言试图将Newsqueak语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦(这也是继承C语言手工管理内存的代价)。在Alef语言之后还有一个名为Limbo的编程语言(地狱的意思),这是一个运行在虚拟机中的脚本语言。Limbo语言是与Go语言最接近的祖先,它和Go语言有着最接近的语法。到设计Go语言时,Rob Pike在CSP并发编程模型的实践道路上已经积累了几十年的经验,关于Go语言并发编程的特性完全是信手拈来,新编程语言的到来也是水到渠成了。

图1-2展示了Go语言库早期代码库日志,可以看出最直接的演化历程(在Git中用git log --before={2008-03-03} --reverse命令查看)

图片

图1-2 Go语言开发日志

从早期提交日志中也可以看出,Go语言是从Ken Thompson发明的B语言、Dennis M. Ritchie发明的C语言逐步演化过来的,它首先是C语言家族的成员,因此很多人将Go语言称为21世纪的C语言。

图1-3给出的是Go语言中来自贝尔实验室特有并发编程基因的演化过程。

图片

图1-3 Go语言并发演化历史

纵观整个贝尔实验室的编程语言的发展进程,从B语言、C语言、Newsqueak、Alef、Limbo语言一路走来,Go语言继承了来自贝尔实验室的半个世纪的软件设计基因,终于完成了C语言革新的使命。纵观这几年来的发展趋势,Go语言已经成为云计算、云存储时代最重要的基础编程语言。

 你好,世界

按照惯例,介绍所有编程语言的第一个程序都是“Hello, World!”。虽然本书假设读者已经了解了Go语言,但是我们还是不想打破这个惯例(因为这个传统正是从Go语言的前辈C语言传承而来的)。下面的代码展示的Go语言程序输出的是中文“你好,世界!”。

1package main
2import "fmt"
3func main() {
4 fmt.Println("你好, 世界!")
5}

将以上代码保存到hello.go文件中。因为代码中有非ASCII的中文字符,我们需要将文件的编码显式指定为无BOM的UTF8编码格式(源文件采用UTF8编码是Go语言规范所要求的)。然后进入命令行并切换到hello.go文件所在的目录。目前我们可以将Go语言当作脚本语言,在命令行中直接输入go run hello.go来运行程序。如果一切正常的话,应该可以在命令行看到输出“你好, 世界!”的结果。

现在,让我们简单介绍一下程序。所有的Go程序都由最基本的函数和变量构成,函数和变量被组织到一个个单独的Go源文件中,这些源文件再按照作者的意图组织成合适的package,最终这些package有机地组成一个完整的Go语言程序。其中,函数用于包含一系列的语句(指明要执行的操作序列),以及执行操作时存放数据的变量。我们这个程序中函数的名字是main。虽然Go语言对函数的名字没有太多的限制,但是main包中的main()函数默认是每一个可执行程序的入口。而package则用于包装和组织相关的函数、变量和常量。在使用一个package之前,我们需要使用import语句导入包。例如,我们这个程序中导入了fmt包(fmt是format的缩写,表示格式化相关的包),然后我们才可以使用fmt包中的Println()函数。

而双引号包含的“你好, 世界!”则是Go语言的字符串面值常量。和C语言中的字符串不同,Go语言中的字符串内容是不可变更的。在以字符串作为参数传递给fmt.Println()函数时,字符串的内容并没有被复制——传递的仅是字符串的地址和长度(字符串的结构在reflect.StringHeader中定义)。在Go语言中,函数参数都是以复制的方式(不支持以引用的方式)传递(比较特殊的是,Go语言闭包函数对外部变量是以引用的方式使用的)

1.2 

“Hello, World”的革命

1.1节中简单介绍了Go语言的演化基因图谱,对其中来自贝尔实验室的特有并发编程基因做了重点介绍,最后引出了Go语言版的“Hello, World”程序。其实“Hello, World”程序是展示各种语言特性的最好的例子,是通向该语言的一个窗口。本节将沿着各个编程语言演化的时间轴(如图1-3所示),简单回顾一下“Hello, World”程序是如何逐步演化到目前的Go语言形式并最终完成它的使命的。

 B语言——Ken Thompson, 1969

首先是B语言,B语言是“Go语言之父”——贝尔实验室的Ken Thompson早年间开发的一种通用的程序设计语言,设计目的是为了用于辅助UNIX系统的开发。但是由于B语言缺乏灵活的类型系统导致使用比较困难。后来,Ken Thompson的同事Dennis Ritchie以B语言为基础开发出了C语言,C语言提供了丰富的类型,极大地增强了语言的表达能力。到目前为止,C语言依然是世界上最常用的程序语言之一。而B语言自从被它取代之后,就只存在于各种文献之中,成为了历史。

目前见到的B语言版本的“Hello, World”,一般认为是来自Brian W. Kernighan编写的B语言入门教程(Go核心代码库中第一个提交者的名字正是Brian W. Kernighan),程序如下:

1main() {
2 extrn a, b, c;
3 putchar(a); putchar(b); putchar(c);
4 putchar('!*n');
5}

6'hell';
7'o, w';
8'orld';

由于B语言缺乏灵活的数据类型,只能分别以全局变量a/b/c来定义要输出的内容,并且每个变量的长度必须对齐到4字节(有一种写汇编语言的感觉)。然后通过多次调用putchar()函数输出字符,最后的'!*n'表示输出一个换行的意思。

总体来说,B语言简单,功能也比较有限。

C语言——Dennis Ritchie,1972—1989

C语言是由Dennis Ritchie在B语言的基础上改进而来,它增加了丰富的数据类型,并最终实现了用它重写UNIX的伟大目标。C语言可以说是现代IT行业最重要的软件基石,目前主流的操作系统几乎全部是由C语言开发的,许多基础系统软件也是C语言开发的。C系家族的编程语言占据统治地位达几十年之久,半个多世纪以来依然充满活力。

在Brian W. Kernighan于1974年左右编写的C语言入门教程中,出现了第一个C语言版本的“Hello, World”程序。这给后来大部分编程语言教程都以“Hello, World”为第一个程序提供了惯例。第一个C语言版本的“Hello, World”程序如下:

1main()
2{
3 printf("hello, world");
4}

关于这个程序,有几点需要说明:首先是main()函数因为没有明确返回值类型,所以默认返回int类型;其次printf()函数默认不需要导入函数声明即可以使用;最后main ()没有明确返回语句,但默认返回0。在这个程序出现时,C语言还远未标准化,我们看到的是早先的C语言语法:函数不用写返回值,函数参数也可以忽略,使用printf ()时不需要包含头文件等。

这个例子同样出现在了1978年出版的《C程序设计语言(第1版)》中,作者正是Brian W. Kernighan和Dennis M. Ritchie(简称K&R)。书中的“Hello, World”末尾增加了一个换行输出:

1main()
2{
3 printf("hello, world
4"
);
5}

这个例子在字符串末尾增加了一个换行,C语言的换行 比B语言的换行'!*n'看起来要简洁了一些。

在K&R的教程面世10年之后的1988年,《C程序设计语言(第2版)》终于出版了。此时ANSI C语言的标准化草案已经初步完成,但正式版本的文档尚未发布。不过书中的“Hello, World”程序根据新的规范增加了#include <stdio.h>头文件包含语句,用于包含printf()函数的声明(新的C89标准中,仅是针对printf()函数而言,依然可以不用声明函数而直接使用)

1#include <stdio.h>
2main()
3{
4 printf("hello, world
5"
);
6}

然后到了1989年,ANSI C语言第一个国际标准发布,一般被称为C89。C89是流行最广泛的一个C语言标准,目前依然被大量使用。《C程序设计语言》也出版了新版本,并针对新发布的C89规范建议,给main()函数的参数增加了void输入参数说明,表示没有输入参数的意思。

1#include <stdio.h>
2main(void)
3{
4 printf("hello, world
5"
);
6}

至此,C语言本身的进化基本完成。后面的C92/C99/C11都只是针对一些语言细节做了完善。因为各种历史因素,C89依然是使用最广泛的标准。

 Newsqueak——Rob Pike, 1989

Newsqueak是Rob Pike发明的老鼠语言的第二代,是他用于实践CSP并发编程模型的战场。Newsqueak是新的Squeak语言的意思,其中squeak是老鼠“吱吱吱”的叫声,也可以看作是类似鼠标点击的声音。Squeak是一个提供鼠标和键盘事件处理的编程语言,Squeak语言的通道是静态创建的。改进版的Newsqueak语言则提供了类似C语言语句和表达式的语法和类似Pascal语言的推导语法。Newsqueak是一个带自动垃圾回收机制的纯函数式语言,它再次针对键盘、鼠标和窗口事件管理。但是在Newsqueak语言中通道是动态创建的,属于第一类值,因此可以保存到变量中。

Newsqueak类似脚本语言,内置了一个print()函数,它的“Hello, World”程序看不出什么特色:

1print("Hello,""World""
2"
);

从上面的程序中,除了猜测print()函数可以支持多个参数,我们很难看到Newsqueak语言相关的特性。由于Newsqueak语言和Go语言相关的特性主要是并发和通道,因此,我们这里通过一个并发版本的“素数筛”算法来略窥Newsqueak语言的特性。“素数筛”的原理如图1-4所示。

图片

图1-4 素数筛

Newsqueak语言并发版本的“素数筛”程序如下:

 1// 向通道输出从2开始的自然数序列
2counter := prog(c:chan of int) {
3 i := 2;
4 for(;;) {
5 c <-= i++;
6 }
7};
8// 针对listen通道获取的数列,过滤掉是prime倍数的数
9// 新的序列输出到send通道
10filter := prog(prime:int, listen, send:chan of int) {
11 i:int;
12 for(;;) {
13 if((i = <-listen)%prime) {
14 send <-= i;
15 }
16 }
17};
18// 主函数
19// 每个通道第一个流出的数必然是素数
20// 然后基于这个新的素数构建新的素数过滤器
21sieve := prog() of chan of int {
22 c := mk(chan of int);
23 begin counter(c);
24 prime := mk(chan of int);
25 begin prog(){
26 p:int;
27 newc:chan of int;
28 for(;;){
29 prime <-= p =<- c;
30 newc = mk();
31 begin filter(p, c, newc);
32 c = newc;
33 }
34 }();
35 become prime;
36};
37// 启动素数筛
38prime := sieve();

其中counter()函数用于向通道输出原始的自然数序列,每个filter()函数对象则对应每一个新的素数过滤通道,这些素数过滤通道根据当前的素数筛将输入通道流入的数列筛选后重新输出到输出通道。mk(chan of int)用于创建通道,类似Go语言的make(chan int)语句;begin filter(p, c, newc)关键字启动素数筛的并发体,类似Go语言的go filter(p, c, newc)语句;become用于返回函数结果,类似return语句。

Newsqueak语言中并发体和通道的语法与Go语言已经比较接近了,后置的类型声明和Go语言的语法也很相似。

Alef——Phil Winterbottom, 1993

在Go语言出现之前,Alef语言是作者心中比较完美的并发语言,Alef语法和运行时基本是无缝兼容C语言。Alef语言中对线程和进程的并发体都提供了支持,其中proc receive(c)用于启动一个进程,task receive(c)用于启动一个线程,它们之间通过通道c进行通信。不过由于Alef缺乏内存自动回收机制,导致并发体的内存资源管理异常复杂。而且Alef语言只在Plan9系统中提供过短暂的支持,其他操作系统并没有实际可以运行的Alef开发环境。而且Alef语言只有《Alef语言规范》和《Alef编程向导》两个公开的文档,因此在贝尔实验室之外关于Alef语言的讨论并不多。

由于Alef语言同时支持进程和线程并发体,而且在并发体中可以再次启动更多的并发体,导致Alef的并发状态异常复杂。同时Alef没有自动垃圾回收机制(Alef保留的C语言灵活的指针特性,也导致自动垃圾回收机制实现比较困难),各种资源充斥于不同的线程和进程之间,导致并发体的内存资源管理异常复杂。Alef语言全部继承了C语言的语法,可以认为是增强了并发语法的C语言。图1-5给出的是Alef语言文档中展示的一个可能的并发体状态。

图片

图1-5 Alef并发模型

Alef语言并发版本的“Hello, World”程序如下:

 1#include <alef.h>
2void receive(chan(byte*) c) {
3 byte *s;
4 s = <- c;
5 print("%s
6"
s);
7 terminate(nil);
8}
9void main(void) {
10 chan(byte*) c;
11 alloc c;
12 proc receive(c);
13 task receive(c);
14 c <- = "hello proc or task";
15 c <- = "hello proc or task";
16 print("done
17"
);
18 terminate(nil);
19}

程序开头的#include <alef.h>语句用于包含Alef语言的运行时库。Receive ()是一个普通函数,用作程序中每个并发体的入口函数;main()函数中的alloc c语句先创建一个chan(byte*)类型的通道,类似Go语言的make(chan []byte)语句;然后分别以进程和线程的方式启动receive()函数;启动并发体之后,main()函数向c通道发送了两个字符串数据;而进程和线程状态运行的receive()函数会以不确定的顺序先后从通道收到数据后,分别打印字符串;最后每个并发体都通过调用terminate(nil)来结束自己。

Alef的语法和C语言基本保持一致,可以认为它是在C语言的语法基础上增加了并发编程相关的特性,可以看作是另一个维度的C++语言。

 Limbo——Sean Dorward, Phil Winterbottom, Rob Pike, 1995

Limbo(地狱)是用于开发运行在小型计算机上的分布式应用的编程语言,它支持模块化编程、编译期和运行时的强类型检查、进程内基于具有类型的通信通道、原子性垃圾收集和简单的抽象数据类型。Limbo被设计为:即便是在没有硬件内存保护的小型设备上,也能安全运行。Limbo语言主要运行在Inferno系统之上。

Limbo语言版本的“Hello, World”程序如下:

 1implement Hello;
2include "sys.m"; sys: Sys;
3include "draw.m";
4Hello: module
5{
6 init: fn(ctxt: ref Draw->Context, args: list of string);
7};
8init(ctxt: ref Draw->Context, args: list of string)
9{
10 sys = load Sys Sys->PATH;
11 sys->print("hello, world
12"
);
13}

从这个版本的“Hello, World”程序中,已经可以发现很多Go语言特性的雏形。第一句implement Hello;基本对应Go语言的包声明语句package Hello。然后是include "sys.m"; sys: Sys;和include "draw.m";语句用于导入其他模块,类似Go语言的import "sys"和import "draw"语句。Hello包模块还提供了模块初始化函数init(),并且函数的参数的类型也是后置的,不过Go语言的初始化函数是没有参数的。

 Go语言——2007—2009

贝尔实验室后来经历了多次动荡,包括Ken Thompson在内的Plan9项目原班人马最终加入了谷歌公司。在Limbo等前辈语言诞生10多年之后,在2007年底,Go语言3个最初的作者因为偶然的因素聚集到一起批斗C++(传说是C++语言的布道师在谷歌公司到处鼓吹C++11各种强大的特性彻底惹恼了他们),他们终于抽出了20%的自由时间创造了Go语言。最初的Go语言规范从2008年3月开始编写,最初的Go程序也是直接编译为C语言,然后再二次编译为机器码。到2008年5月,谷歌公司的领导们终于发现了Go语言的巨大潜力,从而开始全力支持这个项目(谷歌的创始人甚至还贡献了func关键字),让他们可以将全部工作时间投入到Go语言的设计和开发中。在Go语言规范初版完成之后,Go语言的编译器终于可以直接生成机器码了。

1.hello.go——2008年6月

下面是初期Go语言程序正式开始测试的版本:

1package main
2func main() int {
3 print "hello, world
4"
;
5 return 0;
6}

其中内置的用于调试的print语句已经存在,不过是以命令的方式使用的。入口main()函数还和C语言中的main()函数一样返回int类型的值,而且需要return显式地返回值。每个语句末尾的分号也还存在。

2.hello.go——2008年6月27日

下面是2008年6月的Go代码:

1package main
2func main() {
3 print "hello, world
4"
;
5}

入口函数main()已经去掉了返回值,程序默认通过隐式调用exit(0)来返回。Go语言朝着简单的方向逐步进化。

3.hello.go——2008年8月11日

下面是2008年8月的代码:

1package main
2func main() {
3 print("hello, world
4"
);
5}

用于调试的内置的print由开始的命令改为普通的内置函数,使语法更加简单一致。

4.hello.go——2008年10月24日

下面是2008年10月的代码:

1package main
2import "fmt"
3func main() {
4 fmt.printf("hello, world
5"
);
6}

作为C语言中招牌的printf ()格式化函数已经移植到了Go语言中,函数放在fmt包中(fmt是格式化单词format的缩写)。不过printf()函数名的开头字母依然是小写字母,采用大写字母表示导出的特性还没有出现。

5.hello.go——2009年1月15日

下面是2009年1月的代码:

1package main
2import "fmt"
3func main() {
4 fmt.Printf("hello, world
5"
);
6}

Go语言开始采用是否大小写首字母来区分符号是否可以导出。大写字母开头表示导出的公共符号,小写字母开头表示包内部的私有符号。但需要注意的是,汉字中没有大小写字母的概念,因此以汉字开头的符号目前是无法导出的(针对该问题,中国用户已经给出相关建议,等Go 2之后或许会调整对汉字的导出规则)

6.hello.go——2009年12月11日

下面是2009年12月的代码:

1package main
2import "fmt"
3func main() {
4 fmt.Printf("hello, world
5"
)
6}

Go语言终于移除了语句末尾的分号。这是Go语言在2009年11月10日正式开源之后第一个比较重要的语法改进。从1978年C语言教程第一版引入的分号分隔的规则到现在,Go语言的作者们花了整整32年终于移除了语句末尾的分号。在这32年的演化过程中必然充满了各种八卦故事,我想这一定是Go语言设计者深思熟虑的结果(现在Swift等新的语言也是默认忽略分号的,可见分号确实并不是那么重要)

 你好,世界!——V2.0

在经过半个世纪的涅槃重生之后,Go语言不仅打印出了Unicode版本的“Hello, World”,而且可以方便地向全球用户提供打印服务。下面版本通过http服务向每个访问的客户端打印中文的“你好, 世界!”和当前的时间信息。

这里我们通过Go语言标准库自带的net/http包,构造了一个独立运行的HTTP服务。其中http.HandleFunc("/", ...)针对根路径/请求注册了响应处理函数。在响应处理函数中,我们依然使用fmt.Fprintf ()格式化输出函数实现了通过HTTP协议向请求的客户端打印格式化的字符串,同时通过标准库的日志包在服务器端也打印相关字符串。最后通过http.ListenAndServe()函数调用来启动HTTP服务。

至此,Go语言终于完成了从单机单核时代的C语言到21世纪互联网时代多核环境的通用编程语言的蜕变。

本文摘自《GO语言高级编程》

图片

书名:《GO语言高级编程》

作者:柴树杉 曹春晖



编辑推荐:  

  • 一本能满足Gopher好奇心的Go语言进阶读物;

  • 汇集了作者多年来学习和使用Go语言的经验;

  • 更倾向于描述实现细节,极大地满足开发者的探索欲望。

本书聚焦于主流Go语言书中缺失的或刻意回避的部分主题,主要面向希望深入了解Go语言,特别是对Go语言和其他语言的混合编程、Go汇编语言的工作机制、构造Web框架和分布式开发等领域感兴趣的学生、工程师和研究人员。阅读本书需要读者对Go语言有一定的认识和使用经验。 

目录一览


第1章 语言基础 1 

1.1 Go语言创世纪 1 

1.1.1 来自贝尔实验室特有基因 3 

1.1.2 你好,世界 4 

1.2 “Hello, World”的革命 5 

1.2.1 B语言——Ken Thompson, 1969 5 

1.2.2 C语言——Dennis Ritchie,1972—1989 5 

1.2.3 Newsqueak——Rob Pike, 1989 7 

1.2.4 Alef——Phil Winterbottom, 1993 9 

1.2.5 Limbo——Sean Dorward, Phil Winterbottom, Rob Pike, 1995 10 

1.2.6 Go语言——2007—2009 11 

1.2.7 你好,世界!——V2.0 13 

1.3 数组、字符串和切片 13 

1.3.1 数组 14 

1.3.2 字符串 17 

1.3.3 切片 21 

1.4 函数、方法和接口 27 

1.4.1 函数 27 

1.4.2 方法 31 

1.4.3 接口 35 

1.5 面向并发的内存模型 39 

1.5.1 Goroutine和系统线程 40 

1.5.2 原子操作 40 

1.5.3 顺序一致性内存模型 44 

1.5.4 初始化顺序 45 

1.5.5 Goroutine的创建 46 

1.5.6 基于通道的通信 46 

1.5.7 不靠谱的同步 48 

1.6 常见的并发模式 49 

1.6.1 并发版本的“Hello, World” 50 

1.6.2 生产者/消费者模型 52 

1.6.3 发布/订阅模型 53 

1.6.4 控制并发数 56 

1.6.5 赢者为王 57 

1.6.6 素数筛 58 

1.6.7 并发的安全退出 59 

1.6.8 context包 62 

1.7 错误和异常 64 

1.7.1 错误处理策略 65 

1.7.2 获取错误的上下文 67 

1.7.3 错误的错误返回 69 

1.7.4 剖析异常 70 

1.8 补充说明 73 

第2章 CGO编程 74 

2.1 快速入门 74 

2.1.1 最简CGO程序 74 

2.1.2 基于C标准库函数输出字符串 75 

2.1.3 使用自己的C函数 75 

2.1.4 C代码的模块化 76 

2.1.5 用Go重新实现C函数 77 

2.1.6 面向C接口的Go编程 78 

2.2 CGO基础 79 

2.2.1 import "C"语句 79 

2.2.2 #cgo语句 81 

2.2.3 build标志条件编译 82 

2.3 类型转换 83 

2.3.1 数值类型 83 

2.3.2 Go字符串和切片 85 

2.3.3 结构体、联合和枚举类型 86 

2.3.4 数组、字符串和切片 89 

2.3.5 指针间的转换 91 

2.3.6 数值和指针的转换 92 

2.3.7 切片间的转换 93 

2.4 函数调用 94 

2.4.1 Go调用C函数 94 

2.4.2 C函数的返回值 94 

2.4.3 void函数的返回值 95 

2.4.4 C调用Go导出函数 96 

2.5 内部机制 97 

2.5.1 CGO生成的中间文件 97 

2.5.2 Go调用C函数 98 

2.5.3 C调用Go函数 101 

2.6 实战:封装qsort 103 

2.6.1 认识qsort()函数 103 

2.6.2 将qsort()函数从Go包导出 104 

2.6.3 改进:闭包函数作为比较函数 106 

2.6.4 改进:消除用户对unsafe包的依赖 108 

2.7 CGO内存模型 110 

2.7.1 Go访问C内存 110 

2.7.2 C临时访问传入的Go内存 111 

2.7.3 C长期持有Go指针对象 113 

2.7.4 导出C函数不能返回Go内存 115 

2.8 C++类包装 117 

2.8.1 C++类到Go语言对象 117 

2.8.2 Go语言对象到C++类 121 

2.8.3 彻底解放C++的this指针 125 

2.9 静态库和动态库 126 

2.9.1 使用C静态库 126 

2.9.2 使用C动态库 128 

2.9.3 导出C静态库 129 

2.9.4 导出C动态库 131 

2.9.5 导出非main包的函数 131 

2.10 编译和链接参数 133 

2.10.1 编译参数:CFLAGS/CPPFLAGS/CXXFLAGS 133 

2.10.2 链接参数:LDFLAGS 133 

2.10.3 pkg-config 133 

2.10.4 go get链 134 

2.10.5 多个非main包中导出C函数 135 

2.11 补充说明 135 

第3章 Go汇编语言 136 

3.1 快速入门 136 

3.1.1 实现和声明 136 

3.1.2 定义整数变量 137 

3.1.3 定义字符串变量 138 

3.1.4 定义main()函数 141 

3.1.5 特殊字符 141 

3.1.6 没有分号 142 

3.2 计算机结构 142 

3.2.1 图灵机和BrainFuck语言 143 

3.2.2 《人力资源机器》游戏 144 

3.2.3 X86-64体系结构 145 

3.2.4 Go汇编中的伪寄存器 146 

3.2.5 X86-64指令集 147 

3.3 常量和全局变量 150 

3.3.1 常量 150 

3.3.2 全局变量 150 

3.3.3 变量的内存布局 156 

3.3.4 标识符规则和特殊标志 157 

3.3.5 小结 158 

3.4 函数 158 

3.4.1 基本语法 158 

3.4.2 函数参数和返回值 160 

3.4.3 参数和返回值的内存布局 161 

3.4.4 函数中的局部变量 163 

3.4.5 调用其他函数 165 

3.4.6 宏函数 166 

3.5 控制流 167 

3.5.1 顺序执行 167 

3.5.2 if/goto跳转 169 

3.5.3 for循环 171 

3.6 再论函数 172 

3.6.1 函数调用规范 172 

3.6.2 高级汇编语言 173 

3.6.3 PCDATA和FUNCDATA 176 

3.6.4 方法函数 177 

3.6.5 递归函数: 1到n求和 178 

3.6.6 闭包函数 180 

3.7 汇编语言的威力 182 

3.7.1 系统调用 182 

3.7.2 直接调用C函数 184 

3.7.3 AVX指令 185 

3.8 例子:Goroutine ID 187 

3.8.1 故意设计没有goid 187 

3.8.2 纯Go方式获取goid 187 

3.8.3 从g结构体获取goid 189 

3.8.4 获取g结构体对应的接口对象 190 

3.8.5 goid的应用:局部存储 192 

3.9 Delve调试器 194 

3.9.1 Delve入门 194 

3.9.2 调试汇编程序 198 

3.10 补充说明 201 

第4章 RPC和Protobuf 203 

4.1 RPC入门 203 

4.1.1 RPC版“Hello, World” 203 

4.1.2 更安全的RPC接口 205 

4.1.3 跨语言的RPC 207 

4.1.4 HTTP上的RPC 209 

4.2 Protobuf 210 

4.2.1 Protobuf入门 210 

4.2.2 定制代码生成插件 212 

4.2.3 自动生成完整的RPC代码 215 

4.3 玩转RPC 218 

4.3.1 客户端RPC的实现原理 218 

4.3.2 基于RPC实现监视功能 220 

4.3.3 反向RPC 222 

4.3.4 上下文信息 223 

4.4 gRPC入门 224 

4.4.1 gRPC技术栈 225 

4.4.2 gRPC入门 225 

4.4.3 gRPC流 227 

4.4.4 发布和订阅模式 229 

4.5 gRPC进阶 233 

4.5.1 证书认证 233 

4.5.2 Token认证 236 

4.5.3 截取器 238 

4.5.4 和Web服务共存 240 

4.6 gRPC和Protobuf扩展 241 

4.6.1 验证器 241 

4.6.2 REST接口 244 

4.6.3 Nginx 246 

4.7 pbgo:基于Protobuf的框架 246 

4.7.1 Protobuf扩展语法 246 

4.7.2 插件中读取扩展信息 248 

4.7.3 生成REST代码 249 

4.7.4 启动REST服务 250 

4.8 grpcurl工具 251 

4.8.1 启动反射服务 251 

4.8.2 查看服务列表 252 

4.8.3 服务的方法列表 253 

4.8.4 获取类型信息 253 

4.8.5 调用方法 254 

4.9 补充说明 255 

第5章 Go和Web 256 

5.1 Web开发简介 256 

5.2 请求路由 260 

5.2.1 httprouter 260 

5.2.2 原理 262 

5.2.3 压缩检索树创建过程 263 

5.3 中间件 267 

5.3.1 代码泥潭 267 

5.3.2 使用中间件剥离非业务逻辑 269 

5.3.3 更优雅的中间件写法 272 

5.3.4 哪些事情适合在中间件中做 273 

5.4 请求校验 274 

5.4.1 重构请求校验函数 275 

5.4.2 用请求校验器解放体力劳动 276 

5.4.3 原理 277 

5.5 Database 和数据库打交道 279 

5.5.1 从database/sql讲起 279 

5.5.2 提高生产效率的ORM和 

SQL Builder 281 

5.5.3 脆弱的数据库 283 

5.6 服务流量限制 285 

5.6.1 常见的流量限制手段 287 

5.6.2 原理 289 

5.6.3 服务瓶颈和 QoS 291 

5.7 常见大型Web项目分层 291 

5.8 接口和表驱动开发 297 

5.8.1 业务系统的发展过程 297 

5.8.2 使用函数封装业务流程 298 

5.8.3 使用接口来做抽象 298 

5.8.4 接口的优缺点 301 

5.8.5 表驱动开发 303 

5.9 灰度发布和A/B测试 303 

5.9.1 通过分批次部署实现灰度发布 304 

5.9.2 通过业务规则进行灰度发布 305 

5.9.3 如何实现一套灰度发布系统 306 

5.10 补充说明 310 

第6章 分布式系统 311 

6.1 分布式ID生成器 311 

6.1.1 worker_id分配 312 

6.1.2 开源实例 313 

6.2 分布式锁 316 

6.2.1 进程内加锁 317 

6.2.2 尝试锁 317 

6.2.3 基于Redis的setnx 319 

6.2.4 基于ZooKeeper 321 

6.2.5 基于etcd 321 

6.2.6 如何选择合适的锁 322 

6.3 延时任务系统 323 

6.3.1 定时器的实现 323 

6.3.2 任务分发 325 

6.3.3 数据再平衡和幂等考量 326 

6.4 分布式搜索引擎 327 

6.4.1 搜索引擎 328 

6.4.2 异构数据同步 336 

6.5 负载均衡 337 

6.5.1 常见的负载均衡思路 337 

6.5.2 基于洗牌算法的负载均衡 338 

6.5.3 ZooKeeper集群的随机节点挑选问题 340 

6.5.4 负载均衡算法效果验证 340 

6.6 分布式配置管理 341 

6.6.1 场景举例 341 

6.6.2 使用etcd实现配置更新 342 

6.6.3 配置膨胀 345 

6.6.4 配置版本管理 345 

6.6.5 客户端容错 345 

6.7 分布式爬虫 346 

6.7.1 基于colly的单机爬虫 346 

6.7.2 分布式爬虫 347 

6.7.3 结合nats和colly的消息生产 350 

6.7.4 结合colly的消息消费 352 

6.8 补充说明 353 

附录A 使用Go语言常遇到的问题 354 

附录B 有趣的代码片段 363 

附录B 股市是如何运作的:系统的游戏规则 356 

附录C 垃圾进来,垃圾出去:饮食和营养基础知识 362 

附录D 如何吃出健康来:比萨并不是一个食物组 366 




Go语言图书推荐


01 Go语言实战

图片

书名:《Go语言实战》

作者:【美】威廉•肯尼迪(William Kennedy), 布赖恩•克特森(Brian Ketelsen), 埃里克•圣马丁(Erik St. Martin)

译者:李兆海

编辑推荐:  

  • Go语言领域技术专家力作

  • 关注语言的规范和实现

  • 为读者提供一个专注

  • 全面且符合语言习惯的视角

Go语言实战目标读者是已经有一定其他编程语言经验,想要开始学习Go 语言或者更深入了解Go 语言及其内部机制的中级开发者。本书会提供一个专注、全面且符合习惯的视角。本书关注Go 语言的规范和实现,涉及的内容包括语法、Go 的类型系统、并发、通道和测试等主题。

Go语言实战主要内容

  • Go语言的类型系统。

  • Go语言的数据结构的内部实现。

  • 测试和基准测试。

02 Go Web编程

       图片

书名:《Go Web编程》

作者:【新加坡】郑兆雄(Sau Sheong Chang)

译者:黄健宏

编辑推荐:  

  • Go语言Web开发实战教程

  • 囊括了关于Go

本书将教读者运用现代化设计理念构建Go Web应用的方法。阅读本书能让读者学会如何通过依赖注入设计模式来编写测试替身,如何在Web应用中使用并发特性,还有如何在Web服务中创建以及处理JSON数据和XML数据。除此之外,读者还将学会如何尽可能地减少应用对外部框架的依赖,并了解大量与应用测试以及应用部署有关的有价值的生产技术。

本书主要内容

  • 基础知识。

  • 功能测试和基准测试。

  • 并发特性的使用方法。

  • 将应用部署到独立服务器、PaaS云端以及 Docker 的方法。

  • 大量提示、窍门以及技巧。

03 分布式对象存储——原理、架构及Go语言实现

               图片

书名:《分布式对象存储——原理、架构及Go语言实现》

作者:胡世杰

编辑推荐:  

本书从云存储的需求出发讲述对象存储的原理,循序渐进地建立起一个分布式对象存储的架构,并且将软件实现出来。全书共8章,分别涉及对象存储简介、可扩展分布式系统、元数据服务、数据校验和去重、数据冗余处理、断点续传、数据压缩和数据维护等。本书选择用来实现分布式对象存储软件的编程语言是当前流行的Go语言。


END -

 


?


分享时刻


你对本书的看法?

截止7月6日,留言+转发朋友圈

抽取2名读者


图片




图片

为你每日点一首歌


此时此刻,你最想听的歌曲是什么?最想给谁点一首歌?欢迎说出你的故事,异步君,将筛选后,属于你的专属音乐,第二天播放哦。(如果被选中,同时可以获得任意一本异步图书)


 点歌请在此填写表单告诉我哦


➤ 邀请赠书邀请10人关注公众号异步图书10天,即可获得图书一本!

点击表单申请

图片



异步图书

聊聊图书背后的故事

图片



点击阅读原文,直接购书

点个好看增加中奖概率?

67750搬砖民工也会建成自己的“罗马帝国”:Go语言创世纪

这个人很懒,什么都没留下

文章评论