前言:为什么我们需要一门新语言
互联网时代的C语言需要考虑哪些关键问题呢?
- 并行与分布式支持
- 多核化和集群化是互联网时代的典型特
- Go语言在语言级别支持协程(轻量级线程)叫goroutin
执行体间的通信
- 执行体之间的互斥与同步
- 当执行体之间存在共享资源(一般是共享内存)时,为 保证内存访问逻辑的确定性,需要对访问该共享资源的相关执行体进行互斥。
- 当多个执行体之间的逻辑存在时序上的依赖时,也往往需要在执行体之间进行同步。
- 互斥与同步是执行体间最基础 的交互方式
- 执行体之间的消息传递
- 在并发编程模型的选择上,有两个流派,一个是共享内存 模型,一个是消息传递模型。
- 多数传统语言选择了前者,少数语言选择后者
- 其中选择“消息传 递模型”的最典型代表是Erlang语言
- 当执行体之间需要相互传递消息时,通常需要基于一个消 息队列(messagequeue)或者进程邮箱(processmail box)这样的设施进行通信
- 在Go语言中内置了消息队列的支持,只不过它叫通道(channel)。两个goroutine之间可以通过通道来进行交互。
- 执行体之间的互斥与同步
软件工程
- 随着工程规模的不断扩大,软件复杂度的 不断增加,软件工程也成为语言设计层面要考虑的重要课题
- 多数软件需要一个团队共同去完成,在团队协作的过程中,人们需要建立统一的交互语言来降低沟通的成本。
- 规范包括以下方面:
- 代码风格规范
- 错误处理规范
- 包管理
- 契约规范(接口)
- 单元测试规范
- 功能开发的流程规范
- Go语言很可能是第一个将代码风格强制统一的语言,例如GO语言要求public的变量必须以大写字母开头,private变量则以小写字母开头
- Go 语言首创的错误处理规范:
- 其一是defer关键字,defer语句的含义是不管程序是否出现异常,均 在函数退出时自动执行相关代码
- 其二是Go语言的函数允许返回多个值,大多数函数 的最后一个返回值会为error类型,以在错误情况下返回详细信息.
编程哲学
- C语言是纯过程式的,这和它产生的历史背景有关。
- Java语言则是激进的面向对象主义推崇 者,典型表现是它不能容忍体系里存在孤立的函数。
- Go语言没有去否认任何一方,而是用批判吸收的眼光,将所有编程思想做了一次梳理,融合众家之长,但时刻警惕特性复杂化,极力维持语言特性的简洁,力求小而精。
- Go语言反对函数和操作符重载(overload)
- Go语言支持类、类成员方法、类的组合,但反对继承,反对虚函数(virtual function) 和虚函数重载。
- Go语言也放弃了构造函数(constructor)和析构函数(destructor)。由于Go语言中没有虚函数,也就没有vptr,支持构造函数和析构函数就没有太大的价值。
- 本着“如果一个特性并不对解决任何问题有显著的价值,那么Go就不提供它”的原则。
- Go语言的非侵入式接口,看似只是做了很小的文法调整,实则影响深远。
- Go语言的标准库再也不需要绘制类库的继承树图。你只需要知道这个类实现了哪些方法,每个方法是啥含义就足够了
- 不用再纠结接口需要拆得多细才合理。
- 在Go语言中,只要两个接口拥有相同的方法列表,那么它们就是等同的,可以相互赋值。
第 1章 初识 Go语言
- Go语言的主要作者
- 肯·汤普逊(Ken Thompson,http://en.wikipedia.org/wiki/Ken_Thompson):设计了B语言 和C语言,创建了Unix和Plan 9操作系统,1983年图灵奖得主,Go语言的共同作者。
- 罗布·派克(Rob Pike,http://en.wikipedia.org/wiki/Rob_Pike):Unix小组的成员,参与Plan 9和Inferno操作系统,参与 Limbo和Go语言的研发,《Unix编程环境》作者之一。
- 罗伯特·格里泽默(Robert Griesemer):曾协助制作Java的HotSpot编译器和Chrome浏览 器的JavaScript引擎V8。
- 拉斯· 考克斯(Russ Cox,http://swtch.com/~rsc/):参与Plan 9操作系统的开发,Google Code Search项目负责人。
- 伊安·泰勒(Ian Lance Taylor):GCC社区的活跃人物,gold连接器和GCC过程间优化LTO 的主要设计者,Zembu公司的创始人。
- 布拉德·菲茨帕特里克(Brad Fitzpatrick,http://en.wikipedia.org/wiki/Brad_Fitzpatrick):LiveJournal的创始人,著名开源项目memcached的作者。
1.2 语言特性
- 自动垃圾回收
- 更丰富的内置类型
- 函数多返回值
- 错误处理
- 匿名函数和闭包
- 类型和接口
- 并发编程
- 反射
- 语言交互性
1.2.1 自动垃圾回收
- 内存泄露的佳解决方案是在语言级别引入自动垃圾回收算法(Garbage Collection,简称GC)。
- 所谓垃圾回收,即所有的内存分配动作都会被在运行时记录,同时任何对该内存的使用也都会被记录,然后垃圾回收器会对所有已经分配的内存进行跟踪监测,一旦发现 有些内存已经不再被任何人使用,就阶段性地回收这些没人用的内存。
1.2.2 更丰富的内置类型
- 简单内置类型
- 字典类型(map)
- 数组切片(Slice)数组切片是一种可动态增长的数组
- 是语言内置特性,开发者根本不用费事去添加依赖的包,既可以少一些输入工作量,也可以让代码看起来尽量简洁。
1.2.3 函数多返回值
- 主流语言中除Python外基本都不支持函数的多返回值功能
- Go语言革命性地在静态开发语言阵营中率先提供了多返回值功能。
- 这个特性让开发者可以从原来用各种比较别扭的方式返回多个值的痛苦中解脱出来,既不用再区分参数列表中哪几个用 于输入,哪几个用于输出,也不用再只为了返回多个值而专门定义一个数据结构。
package demo
func getName()(firstName, middleName, lastName, nickName string){
return "May", "M", "Chen", "Babe"
}
1.2.4 错误处理
- Go语言引入了3个关键字用于标准的错误处理流程,这3个关键字分别为defer、panic和 recover。
- Go语言的错误处理机制可以大量减少代码量,让开发者也无需仅仅为了程序安全性而添加大量一层套一层的try-catch语句。
- 对于代码的阅读者和维护者来说也是一件很好的事情,因为可 以避免在层层的代码嵌套中定位业务代码。
1.2.5 匿名函数和闭包
- 在Go语言中,所有的函数也是值类型,可以作为参数传递。
- Go语言支持常规的匿名函数和闭包。
f := func(x, y int) int { return x + y }
1.2.6 类型和接口
- Go语言的类型定义非常接近于C语言中的结构(struct),甚至直接沿用了struct关键字。
- 不支持继承 和重载,而只是支持了基本的类型组合功能。
- 引入了一个无比强大的“非侵入式” 接口的概念。
type Bird struct { ... }
func (b *Bird) Fly() { // 以鸟的方式飞行 }
- 我们在实现Bird类型时完全没有任何IFly的信息。我们可以在另外一个地方定义这个IFly 接口:
type IFly interface { Fly() }
- 这两者目前看起来完全没有关系,现在看看我们如何使用它们:
func main(){
var fly IFly = new (Bird)
fly.Fly()
}
- 可以看出,虽然Bird类型实现的时候,没有声明与接口IFly的关系,但接口和类型可以直接转换,甚至接口的定义都不用在类型定义之前。
- 这种比较松散的对应关系可以大幅降低因为接 口调整而导致的大量代码调整工作。
1.2.7 并发编程
- Go语言引入了goroutine概念,它使得并发编程变得非常简单
- 通过使用goroutine而不是裸用操作系统的并发机制,以及使用消息传递来共享内存而不是使用共享内存来通信。
- Go语言让并 发编程变得更加轻盈和安全。
- 通过在函数调用前使用关键字go,我们即可让该函数以goroutine方式执行。
- goroutine是一种比线程更加轻盈、更省资源的协程
- 调度的开销 非常小,一颗CPU调度的规模不下于每秒百万次,这使得我们能够创建大量的goroutine,从而可 以很轻松地编写高并发程序,达到我们想要的目的。
- Go语言实现了CSP(通信顺序进程,Communicating Sequential Process)模型来作为goroutine 间的推荐通信方式。
- 在CSP模型中,一个并发系统由若干并行运行的顺序进程组成,每个进程不能对其他进程的变量赋值,进程之间只能通过一对通信原语实现协作。
- Go语言用channel(通道)这个概念来轻巧地实现了CSP模型。channel的使用方式比较接近Unix系统中的管道(pipe)概念, 可以方便地进行跨goroutine的通信。
- 由于一个进程内创建的所有goroutine运行在同一个内存地址空间中,因此如果不同的goroutine不得不去访问共享的内存变量,访问前应该先获取相应的读写锁。Go语言标准库中的sync包提供了完备的读写锁功能。
package main
import "fmt"
func sum(values [] int ,resultChan chan int){
sum:=0
for _,value:= range values {
sum += value
}
resultChan <-sum //将计算结果发送到channel中
}
func main() {
values:= [] int{1,2,4,5,6,7,8,9,10}
resultChan :=make (chan int ,2)
go sum(values[:len(values)/2],resultChan)
go sum(values[len(values)/2:],resultChan)
sum1, sum2 := <-resultChan, <-resultChan //接受结果
fmt.Println("results:",sum1,sum2,sum1+sum2)
}
1.2.8 反射
- 反射(reflection)是在Java语言出现后迅速流行起来的一种概念。
- 你可以获取对 象类型的详细信息,并可动态操作对象。
- Go语言的反射实现了反射的大部分功能,但没有像Java语言那样内置类型工厂,故而无法做到像Java那样通过类型字符串创建对象实例。
- Go语言标准库的encoding/json、encoding/xml、encoding/gob、encoding/binary等包就大量依赖于反射功能来实现。
1.2.9 语言交互性
- 在Go代码中,可以按Cgo的特定语法混合编写C语言代码
1.3 第一个 Go程序
- 每个Go源代码文件的开头都是一个package声明,表示该Go代码所属的包
- Go语言的main()函数不能带参数,也不能定义返回值
- 要生成Go可执行程序,必须建立一个名 字为main的包,并且在该包中包含一个叫main()的函数(该函数是Go可执行程序的执行起点)。
- GO函数
func 函数名(参数列表)(返回值列表) { // 函数体 }
func Compute(value1 int, value2 float64)(result float64, err error) { // 函数体 }
- Go支持多个返回值。以上的示例函数Compute()返回了两个值,一个叫result,另一个是 err。并不是所有返回值都必须赋值。在函数返回时没有被明确赋值的返回值都会被设置为默认 值,比如result会被设为0.0,err会被设为nil。
1.4开发工具选择
- 文本编辑工具gedit(Linux)/Notepad++(Windows)/Fraise(Mac OS X);
- 安装了GoClipse插件的Eclipse,集成性做得很好;
- Vim/Emacs,万能开发工具
- LiteIDE,一款专为Go语言开发的集成开发环境。
- Visual Studio Code
1.5 工程管理
1.6 问题追踪和调试
- 打印日志和使用GDB进行逐步调试。
1.6.1 打印日志
- Go语言包中包含一个fmt包,其中提供了大量易用的打印函数。
1.6.2 GDB调试
1.7.1 邮件列表
- Go邮件组的地址为http://groups.google.com/group/golang-nuts
- Go的中文邮件组为http://groups.google.com/group/golang-china
1.7.2 网站资源
- Go语言的官方网站为 http://golang.org
- 下载最新代码 http://code.google.com/p/go/
- 持续对Go资料进行了整理:http://github.com/wonderfo/wonderfogo/wiki
参考资料:《GO语言编程》 The Go Programming Language 许式伟@编著