查看原文
其他

最具潜力的编程语言GO有新书啦!

脚本之家 2021-06-29

脚本之家

你与百万开发者在一起


互联网时代的来临,改变甚至颠覆了很多东西。从前,一台主机就能搞定一切;而在互联网时代,后台由大量分布式系统构成,任何单个后台服务器节点的故障都不会影响整个系统的正常运行。

以七牛云、阿里云和腾讯云为代表的云厂商的出现和崛起,标志着云时代的到来。在云时代,掌握分布式编程已经成为软件工程师的基本技能,而基于Go语言构建的Docker、Kubernetes等系统正是将云时代推向顶峰的关键力量。

Go语言已历经十年,最初的追随者也已经逐渐成长为Go语言资深用户。随着资深用户的不断积累,Go语言相关教程随之增加,在内容层面主要涵盖Go语言基础编程、Web编程、并发编程和内部源码剖析等诸多领域。

今天把新书《GO语言高级编程》推荐给小伙伴们!!

 《GO语言高级编程》  


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

作者:  柴树杉  曹春晖

编辑推荐:


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

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

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

本书作者是国内第一批Go语言实践者和Go语言代码贡献者,创建了Go语言中国讨论组,并组织了早期Go语言相关中文文档的翻译工作。作者从2011年开始分享Go语言和C/C++语言混合编程技术。本书汇集了作者多年来学习和使用Go语言的经验,内容涵盖CGO特性、Go汇编语言、RPC实现、Protobuf插件实现、Web框架实现、分布式系统等高阶主题。其中,CGO特性实现了Go语言对C语言和C++语言混合编程的支持,使Go语言可以无缝继承C/C++世界数十年来积累的巨大软件资产。Go汇编语言更是提供了直接调用底层机器指令的方法,让我们可以最大限度地提升程序中热点代码的性能。

本书适合有一定Go语言经验,并想深入了解Go语言各种高级用法的开发人员。对于Go语言新手,建议在阅读本书前先阅读一些基础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, 19695 

1.2.2 C语言——Dennis Ritchie,1972—19895 

1.2.3 Newsqueak——Rob Pike, 19897 

1.2.4 Alef——Phil Winterbottom, 19939 

1.2.5 Limbo——Sean Dorward, Phil Winterbottom, Rob Pike, 199510 

1.2.6 Go语言——2007—200911 

1.2.7 你好,世界!——V2.013 

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 实战:封装qsort103 

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/CXXFLAGS133 

2.10.2 链接参数:LDFLAGS133 

2.10.3 pkg-config133 

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和FUNCDATA176 

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 ID187 

3.8.1 故意设计没有goid187 

3.8.2 纯Go方式获取goid187 

3.8.3 从g结构体获取goid189 

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和Protobuf203 

4.1 RPC入门203 

4.1.1 RPC版“Hello, World”203 

4.1.2 更安全的RPC接口205 

4.1.3 跨语言的RPC207 

4.1.4 HTTP上的RPC209 

4.2 Protobuf210 

4.2.1 Protobuf入门210 

4.2.2 定制代码生成插件212 

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

4.3 玩转RPC218 

4.3.1 客户端RPC的实现原理218 

4.3.2 基于RPC实现监视功能220 

4.3.3 反向RPC222 

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 Nginx246 

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和Web256 

5.1 Web开发简介256 

5.2 请求路由260 

5.2.1 httprouter260 

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 Builder281 

5.5.3 脆弱的数据库283 

5.6 服务流量限制285 

5.6.1 常见的流量限制手段287 

5.6.2 原理289 

5.6.3 服务瓶颈和 QoS291 

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的setnx319 

6.2.4 基于ZooKeeper321 

6.2.5 基于etcd321 

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



样章试读:第1章 语言基础(截选)   


1.3

数组、字符串和切片

在主流的编程语言中数组及其相关的数据结构是使用得最为频繁的,只有在它(们)不能满足时才会考虑链表、散列表(散列表可以看作是数组和链表的混合体)和更复杂的自定义数据结构。

Go语言中数组、字符串和切片三者是密切相关的数据结构。这3种数据类型,在底层原始数据有着相同的内存结构,在上层,因为语法的限制而有着不同的行为表现。首先,Go语言的数组是一种值类型,虽然数组的元素可以被修改,但是数组本身的赋值和函数传参都是以整体复制的方式处理的。Go语言字符串底层数据也是对应的字节数组,但是字符串的只读属性禁止了在程序中对底层字节数组的元素的修改。字符串赋值只是复制了数据地址和对应的长度,而不会导致底层数据的复制。切片的行为更为灵活,切片的结构和字符串结构类似,但是解除了只读限制。切片的底层数据虽然也是对应数据类型的数组,但是每个切片还有独立的长度和容量信息,切片赋值和函数传参时也是将切片头信息部分按传值方式处理。因为切片头含有底层数据的指针,所以它的赋值也不会导致底层数据的复制。其实Go语言的赋值和函数传参规则很简单,除闭包函数以引用的方式对外部变量访问之外,其他赋值和函数传参都是以传值的方式处理。要理解数组、字符串和切片这3种不同的处理方式的原因,需要详细了解它们的底层数据结构。

1.3.1 数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。数组的长度是数组类型的组成部分。因为数组的长度是数组类型的一部分,不同长度或不同类型的数据组成的数组都是不同的类型,所以在Go语言中很少直接使用数组(不同长度的数组因为类型不同无法直接赋值)。和数组对应的类型是切片,切片是可以动态增长和收缩的序列,切片的功能也更加灵活,但是要理解切片的工作原理还是要先理解数组。

我们先看看数组有哪些定义方式:

1var a [3]int                       // 定义长度为3的int型数组,元素全部为0
2var b = [...]int{123}          // 定义长度为3的int型数组,元素为1, 2, 3
3var c = [...]int{2312}       // 定义长度为3的int型数组,元素为0, 2, 3
4var d = [...]int{12456}    // 定义长度为6的int型数组,元素为1, 2, 0, 0, 5, 6

第一种方式是定义一个数组变量的最基本的方式,数组的长度明确指定,数组中的每个元素都以零值初始化。

第二种方式是定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长度根据初始化元素的数目自动计算。

第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比较随意。这种初始化方式和map[int]Type类型的初始化语法类似。数组的长度以出现的最大的索引为准,没有明确初始化的元素依然用零值初始化。

第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始化,第三个和第四个元素采用零值初始化,第五个元素通过索引初始化,最后一个元素跟在前面的第五个元素之后采用顺序初始化。

数组的内存结构比较简单。例如,图1-6给出的是一个[4]int{2,3,5,7}数组值对应的内存结构。

图1-6 数组布局

Go语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式地指向第一个元素的指针(例如C语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。

1var a = [...]int{123// a是一个数组
2var b = &a                // b是指向数组的指针
3
4fmt.Println(a[0], a[1])   // 打印数组的前两个元素
5fmt.Println(b[0], b[1])   // 通过数组指针访问数组元素的方式和通过数组类似
6
7for i, v := range b {     // 通过数组指针迭代数组的元素
8    fmt.Println(i, v)
9}

其中b是指向数组a的指针,但是通过b访问数组中元素的写法和a是类似的。还可以通过for range来迭代数组指针指向的数组元素。其实数组指针类型除类型和数组不同之外,通过数组指针操作数组的方式和通过数组本身的操作类似,而且数组指针赋值时只会复制一个指针。但是数组指针类型依然不够灵活,因为数组的长度是数组类型的组成部分,指向不同长度数组的数组指针类型也是完全不同的。

可以将数组看作一个特殊的结构体,结构的字段名对应数组的索引,同时结构体成员的数目是固定的。内置函数len()可以用于计算数组的长度,cap()函数可以用于计算数组的容量。不过对数组类型来说,len()cap()函数返回的结果始终是一样的,都是对应数组类型的长度。

我们可以用for循环来迭代数组。下面常见的几种方式都可以用来遍历数组:

1for i := range a {
2    fmt.Printf("a[%d]: %d
"
, i, a[i])
3}
4for i, v := range b {
5    fmt.Printf("b[%d]: %d
"
, i, v)
6}
7for i := 0; i < len(c); i++ {
8    fmt.Printf("c[%d]: %d
"
, i, c[i])
9}

for range方式迭代的性能可能会更好一些,因为这种迭代可以保证不会出现数组越界的情形,每轮迭代对数组元素的访问时可以省去对下标越界的判断。

for range方式迭代,还可以忽略迭代时的下标:

1var times [5][0]int
2for range times {
3    fmt.Println("hello")
4}

其中times对应一个[5][0]int类型的数组,虽然第一维数组有长度,但是数组的元素[0]int大小是0,因此整个数组占用的内存大小依然是0。不用付出额外的内存代价,我们就通过for range方式实现times次快速迭代。

数组不仅可以定义数值数组,还可以定义字符串数组、结构体数组、函数数组、接口数组、通道数组等:

1// 字符串数组
2var s1 = [2]string{"hello""world"}
3var s2 = [...]string{"你好""世界"}
4var s3 = [...]string{1"世界"0"你好", }
5
6// 结构体数组
7var line1 [2]image.Point
8var line2 = [...]image.Point{image.Point{X: 0, Y: 0}, image.Point{X: 1, Y: 1}}
9var line3 = [...]image.Point{{00}, {11}}
10
11// 函数数组
12var decoder1 [2]func(io.Reader) (image.Image, error)
13var decoder2 = [...]func(io.Reader) (image.Image, error)
{
14    png.Decode,
15    jpeg.Decode,
16}
17
18// 接口数组
19var unknown1 [2]interface{}
20var unknown2 = [...]interface{}{123"你好"}
21
22// 通道数组
23var chanList = [2]chan int{}

我们还可以定义一个空的数组:

1var d [0]int         // 定义一个长度为0的数组
2var e = [0]int{}     // 定义一个长度为0的数组
3var f = [...]int{}   // 定义一个长度为0的数组

长度为0的数组(空数组)在内存中并不占用空间。空数组虽然很少直接使用,但是可以用于强调某种特有类型的操作时避免分配额外的内存空间,例如用于通道的同步操作:

1c1 := make(chan [0]int)
2go func() {
3    fmt.Println("c1")
4    c1 <- [0]int{}
5}()
6<-c1

在这里,我们并不关心通道中传输数据的真实类型,其中通道接收和发送操作只是用于消息的同步。对于这种场景,我们用空数组作为通道类型可以减少通道元素赋值时的开销。当然,一般更倾向于用无类型的匿名结构体代替空数组:

1c2 := make(chan struct{})
2go func() {
3    fmt.Println("c2")
4    c2 <- struct{}{} // struct{}部分是类型,{}表示对应的结构体值
5}()
6<-c2

我们可以用fmt.Printf()函数提供的%T%#v谓词语法来打印数组的类型和详细信息:

1fmt.Printf("b: %T
"
, b)  // b: [3]int
2fmt.Printf("b: %#v
"
, b) // b: [3]int{123}

在Go语言中,数组类型是切片和字符串等结构的基础。以上对于数组的很多操作都可以直接用于字符串或切片中。

没看过瘾,可以移步这里购买

更多好书请关注脚本之家官方书店


更多精彩


在公众号后台对话框输入以下关键词

查看更多优质内容!


女朋友 | 大数据 | 运维 | 书单 | 算法

大数据 | JavaScript | Python | 黑客

AI | 人工智能 | 5G | 区块链

机器学习 | 数学 | 留言送书

●  爬虫入门:如何用python爬取网易新闻?

●  脚本之家粉丝福利,请查看!

●  那些裸辞的程序员,都干嘛去了?

● 致敬经典:Linux/UNIX必读书单推荐给你

● 6月份GitHub上最热门的开源项目!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存