《Go 语言程序设计》(Mark Summerfield)电子书网盘下载免费在线阅读
创新互联建站是一家专注于成都网站建设、成都做网站与策划设计,内江网站建设哪家好?创新互联建站做网站,专注于网站建设10余年,网设计领域的专业建站公司;建站业务涵盖:内江等地区。内江做网站价格咨询:18982081108
资源链接:
链接:
提取码: kqzi
书名:Go 语言程序设计
作者:Mark Summerfield
译者:许式伟
豆瓣评分:6.9
出版社:人民邮电出版社
出版年份:2013-8-1
页数:354
内容简介:
国外最经典的Go语言著作,Go语言编程的先驱者Mark Summerfield的实践经验总结。
这是一本Go语言实战指南,帮你了解Go语言,按Go语言的方式思考,以及使用Go语言来编写高性能软件。
作者展示了如何编写充分利用Go语言突破性的特性和惯用法的代码,以及Go语言在其他语言之上所做的改进,并着重强调了Go语言的关键创新。
注重实践教学,每章都提供了多个经过精心设计的代码示例。
由国内第一个核心服务完全采用Go语言实现的团队——七牛团队核心成员翻译。
作者简介:
Mark Summerfield Qtrac公司创始人,独立的培训讲师、顾问、技术编辑,Go、Python、C++、Qt和PyQt方面的技术作家。他的著作包括Rapid GUI Programming with Python and Qt、C++ GUI Programming with Qt 4(与Jasmin Blanchette合著)、Programming in Python 3和Advanced Qt Programming等。
许式伟——七牛云存储CEO,开源爱好者,发布过十余个C++开源项目,拥有超过15年的C/C++开发经验。
吕桂华——七牛云存储联合创始人,拥有十余年的C/C++大型项目开发经验,也曾在Java和.NET平台上探索多年。
徐 立——七牛云存储首席布道师,前盛大创新院高级研究员。
何李石——七牛云存储布道师。
七牛云存储技术团队是国内第一个核心服务完全采用Go语言实现的团队。
已经有好多程序员都把Go语言描述为是一种所见即所得(WYSIWYG)的编程语言。这是说,代码要做的事和它在字面上表达的意思是完全一致的。 在这些新语言中,包含D,Go,Rust和Vala语言,Go曾一度出现在TIOBE的排行榜上面。与其他新语言相比,Go的魅力明显要大很多。Go的成熟特征会得到许多开发者的欣赏,而不仅仅是因为其夸大其词的曝光度。下面我们来一起探讨一下谷歌开发的Go语言以及谈谈Go为什么会吸引众多开发者: 快速简单的编译 Go编译速度很快,如此快速的编译使它很容易作为脚本语言使用。关于编译速度快主要有以下几个原因:首先,Go不使用头文件;其次如果一个模块是依赖A的,这反过来又取决于B,在A里面的需求改变只需重新编译原始模块和与A相依赖的地方;最后,对象模块里面包含了足够的依赖关系信息,所以编译器不需要重新创建文件。你只需要简单地编译主模块,项目中需要的其他部分就会自动编译,很酷,是不是? 通过返回数值列表来处理错误信息 目前,在本地语言里面处理错误的方式主要有两种:直接返回代码或者抛异常。这两种都不是最理想的处理方式。其中返回代码是非常令人沮丧的,因为返回的错误代码经常与从函数中返回的数据相冲突。Go允许函数返回多个值来解决这个问题。这个从函数里面返回的值,可以用来检查定义的类型是否正确并且可以随时随地对函数的返回值进行检查。如果你对错误值不关心,你可以不必检查。在这两种情况下,常规的返回值都是可用的。 简化的成分(优先于继承) 通过使用接口,类型是有资格成为对象中一员的,就像Java指定行为一样。例如在标准库里面的IO包,定义一个Writer来指定一个方法,一个Writer函数,其中输入参数是字节数组并且返回整数类型值或者错误类型。任何类型实现一个带有相同签名的Writer方法是对IO的完全实现,Writer接口。这种是解耦代码而不是优雅。它还简化了模拟对象来进行单元测试。例如你想在数据库对象中测试一个方法,在标准语言中,你通常需要创建一个数据库对象,并且需要进行大量的初始化和协议来模拟对象。在Go里面,如果该方法需要实现一个接口,你可以创建任何对该接口有用的对象,所以,你创建了MockDatabase,这是很小的对象,只实现了几个需要运行和模拟的接口——没有构造函数,没有附件功能,只是一些方法。 简化的并发性 相对于其他语言,并发性在Go里面显得更加容易。把‘go’关键字放在任意函数前面然后那个函数就会在其go-routine自动运行(一个很轻的线程)。go-routines是通过通道进行交流并且基本上封锁了所有的队列消息。普通工具对相互排斥是有用,但是Go通过使用通道来踢掉并发性任务和坐标更加容易。 优秀的错误消息 所有与Go相似的语言,自身作出的诊断都是无法与Go相媲美的。例如,一个死锁程序,在Go运行时会通知你目前哪个线程导致了这种死锁。编译的错误信息是非常详细全面和有用的。 其他 这里还有许多其他吸引人的地方,下面就一概而过的介绍一下,比如高阶函数、垃圾回收、哈希映射和可扩展的数组内置语言(部分语言语法,而不是作为一个库)等等。 当然,Go并不是完美无瑕。在工具方面还有些不成熟的地方和用户社区较小等,但是随着谷歌语言的不断发展,肯定会有整治措施出来。尽管许多语言,尤其是D、Rust和Vala旨在简化C++并且对其进行简化,但它们给人的感觉仍是“C++看上去要更好”。
【Go语言的优势】
可直接编译成机器码,不依赖其他库,glibc的版本有一定要求,部署就是扔一个文件上去就完成了。
静态类型语言,但是有动态语言的感觉,静态类型的语言就是可以在编译的时候检查出来隐藏的大多数问题,动态语言的感觉就是有很多的包可以使用,写起来的效率很高。
语言层面支持并发,这个就是Go最大的特色,天生的支持并发,我曾经说过一句话,天生的基因和整容是有区别的,大家一样美丽,但是你喜欢整容的还是天生基因的美丽呢?Go就是基因里面支持的并发,可以充分的利用多核,很容易的使用并发。
内置runtime,支持垃圾回收,这属于动态语言的特性之一吧,虽然目前来说GC不算完美,但是足以应付我们所能遇到的大多数情况,特别是Go1.1之后的GC。
简单易学,Go语言的作者都有C的基因,那么Go自然而然就有了C的基因,那么Go关键字是25个,但是表达能力很强大,几乎支持大多数你在其他语言见过的特性:继承、重载、对象等。
丰富的标准库,Go目前已经内置了大量的库,特别是网络库非常强大,我最爱的也是这部分。
内置强大的工具,Go语言里面内置了很多工具链,最好的应该是gofmt工具,自动化格式化代码,能够让团队review变得如此的简单,代码格式一模一样,想不一样都很困难。
跨编译,如果你写的Go代码不包含cgo,那么就可以做到window系统编译linux的应用,如何做到的呢?Go引用了plan9的代码,这就是不依赖系统的信息。
内嵌C支持,前面说了作者是C的作者,所以Go里面也可以直接包含c代码,利用现有的丰富的C库。
Go 语言较之 C 语言一个很大的优势就是自带 GC 功能,可 GC 并不是没有代价的。写 C 语言的时候,在一个函数内声明的变量,在函数退出后会自动释放掉,因为这些变量分配在栈上。如果你期望变量的数据可以在函数退出后仍然能被访问,就需要调用 malloc 方法在堆上申请内存,如果程序不再需要这块内存了,再调用 free 方法释放掉。Go 语言不需要你主动调用 malloc 来分配堆空间,编译器会自动分析,找出需要 malloc 的变量,使用堆内存。编译器的这个分析过程就叫做逃逸分析。
所以你在一个函数中通过 dict := make(map[string]int) 创建一个 map 变量,其背后的数据是放在栈空间上还是堆空间上,是不一定的。这要看编译器分析的结果。
可逃逸分析并不是百分百准确的,它有缺陷。有的时候你会发现有些变量其实在栈空间上分配完全没问题的,但编译后程序还是把这些数据放在了堆上。如果你了解 Go 语言编译器逃逸分析的机制,在写代码的时候就可以有意识地绕开这些缺陷,使你的程序更高效。
Go 语言虽然在内存管理方面降低了编程门槛,即使你不了解堆栈也能正常开发,但如果你要在性能上较真的话,还是要掌握这些基础知识。
这里不对堆内存和栈内存的区别做太多阐述。简单来说就是, 栈分配廉价,堆分配昂贵。 栈空间会随着一个函数的结束自动释放,堆空间需要时间 GC 模块不断地跟踪扫描回收。如果对这两个概念有些迷糊,建议阅读下面 2 个文章:
这里举一个小例子,来对比下堆栈的差别:
stack 函数中的变量 i 在函数退出会自动释放;而 heap 函数返回的是对变量 i 的引用,也就是说 heap() 退出后,表示变量 i 还要能被访问,它会自动被分配到堆空间上。
他们编译出来的代码如下:
逻辑的复杂度不言而喻,从上面的汇编中可看到, heap() 函数调用了 runtime.newobject() 方法,它会调用 mallocgc 方法从 mcache 上申请内存,申请的内部逻辑前面文章已经讲述过。堆内存分配不仅分配上逻辑比栈空间分配复杂,它最致命的是会带来很大的管理成本,Go 语言要消耗很多的计算资源对其进行标记回收(也就是 GC 成本)。
Go 编辑器会自动帮我们找出需要进行动态分配的变量,它是在编译时追踪一个变量的生命周期,如果能确认一个数据只在函数空间内访问,不会被外部使用,则使用栈空间,否则就要使用堆空间。
我们在 go build 编译代码时,可使用 -gcflags '-m' 参数来查看逃逸分析日志。
以上面的两个函数为例,编译的日志输出是:
日志中的 i escapes to heap 表示该变量数据逃逸到了堆上。
需要使用堆空间,所以逃逸,这没什么可争议的。但编译器有时会将 不需要 使用堆空间的变量,也逃逸掉。这里是容易出现性能问题的大坑。网上有很多相关文章,列举了一些导致逃逸情况,其实总结起来就一句话:
多级间接赋值容易导致逃逸 。
这里的多级间接指的是,对某个引用类对象中的引用类成员进行赋值。Go 语言中的引用类数据类型有 func , interface , slice , map , chan , *Type(指针) 。
记住公式 Data.Field = Value ,如果 Data , Field 都是引用类的数据类型,则会导致 Value 逃逸。这里的等号 = 不单单只赋值,也表示参数传递。
根据公式,我们假设一个变量 data 是以下几种类型,相应的可以得出结论:
下面给出一些实际的例子:
如果变量值是一个函数,函数的参数又是引用类型,则传递给它的参数都会逃逸。
上例中 te 的类型是 func(*int) ,属于引用类型,参数 *int 也是引用类型,则调用 te(j) 形成了为 te 的参数(成员) *int 赋值的现象,即 te.i = j 会导致逃逸。代码中其他几种调用都没有形成 多级间接赋值 情况。
同理,如果函数的参数类型是 slice , map 或 interface{} 都会导致参数逃逸。
匿名函数的调用也是一样的,它本质上也是一个函数变量。有兴趣的可以自己测试一下。
只要使用了 Interface 类型(不是 interafce{} ),那么赋值给它的变量一定会逃逸。因为 interfaceVariable.Method() 先是间接的定位到它的实际值,再调用实际值的同名方法,执行时实际值作为参数传递给方法。相当于 interfaceVariable.Method.this = realValue
向 channel 中发送数据,本质上就是为 channel 内部的成员赋值,就像给一个 slice 中的某一项赋值一样。所以 chan *Type , chan map[Type]Type , chan []Type , chan interface{} 类型都会导致发送到 channel 中的数据逃逸。
这本来也是情理之中的,发送给 channel 的数据是要与其他函数分享的,为了保证发送过去的指针依然可用,只能使用堆分配。
可变参数如 func(arg ...string) 实际与 func(arg []string) 是一样的,会增加一层访问路径。这也是 fmt.Sprintf 总是会使参数逃逸的原因。
例子非常多,这里不能一一列举,我们只需要记住分析方法就好,即,2 级或更多级的访问赋值会 容易 导致数据逃逸。这里加上 容易 二字是因为随着语言的发展,相信这些问题会被慢慢解决,但现阶段,这个可以作为我们分析逃逸现象的依据。
下面代码中包含 2 种很常规的写法,但他们却有着很大的性能差距,建议自己想下为什么。
Benchmark 和 pprof 给出的结果:
熟悉堆栈概念可以让我们更容易看透 Go 程序的性能问题,并进行优化。
多级间接赋值会导致 Go 编译器出现不必要的逃逸,在一些情况下可能我们只需要修改一下数据结构就会使性能有大幅提升。这也是很多人不推荐在 Go 中使用指针的原因,因为它会增加一级访问路径,而 map , slice , interface{} 等类型是不可避免要用到的,为了减少不必要的逃逸,只能拿指针开刀了。
大多数情况下,性能优化都会为程序带来一定的复杂度。建议实际项目中还是怎么方便怎么写,功能完成后通过性能分析找到瓶颈所在,再对局部进行优化。