一点体会
下面这篇博文是在看《go语言编程》书的笔记。 在看书的过程中,其实也没有对go语言进行深入的学习。仅仅是停留在对语法的简单了解。
总的来说,go语言没有它多的新东西,仅仅是将各个语言比较有特色的内容,集中到以一个语言中,而且还是基于C语言的,因为go语言的作者就是C语言的作者。哪些比较有特色的呢,例如闭包,接口,垃圾回收,还有必然语言级别支持协程。这种炒大杂烩的方式,个人感觉不可能会成功。只不过go语言已一个比较强大的干爹google,所有才多多少少掀起了几个波浪。
很有意思的一件事情是,虽然这个语言生在美国,生在google,但是目前go语言的社区最活跃的,还是我们中国的屌丝程序员。我认为这是一件极好的事情,说明了我们中国在IT方面对新事物的开明态度和勇于追逐,虽然成功可能不在go语言,但是有这种态度,终会有所作为。
go语言简介
go语言是google推出的一个可以提高并发编程的语言,它着不同一般的背景。
- 回溯至1969 年, 肯·汤普逊(Ken Thompson)和丹尼斯·里奇(Dennis Ritchie )在贝尔实验室的计算科学研究中心里开发出了Unix ,还因为开发Unix而衍生——C语言。
- 80年代,开始Plan 9 的操作系统研究项目,解决Unix 中的一些问题, 又演变出了Inferno 的项目分支,以及一个名为Limbo 的编程语言
- Limbo是用于开发运行在小型计算机上的分布式应用的编程语言,它支持模块化编程,编译期和运行时的强类型检查,进程内基于具有类型的通信通道,原子性垃圾收集和简单的抽象数据类型。它被设计为:即便是在没有硬件内存保护的小型设备上,也能安全运行。
- Limbo 语言被认为是Go语言的前身,不仅仅因为是同一批人设计的语言,而是Go语言确实从Limbo 语言中继承了众多优秀的特性。
- 贝尔实验室后来经历了多次的动荡,包括肯·汤普逊在内的Plan 9 项目原班人马加入了Google 。在Google ,他们创造了Go语言。
- 2007 年9月,Go语言还是这帮大牛的20% 自由时间的实验项目
- 2008 年5月,Google 发现了Go语言的巨大潜力,从而开始全力支持这个项目
- 2009年11 月,发布第一个版本在
- 2012年3月28 日,发布第一个正式版本
go语言特性
- 自动垃圾回收
- 更丰富的内置类型
- 函数多返回值
- 错误处理
- 匿名函数和闭包
- 类型和接口
- 并发编程
- 反射
- 语言交互性 (Cgo, C语言库)
go的工具
- 编辑器
- 文本编辑工具gedit(Linux)/Notepad++ (Windows)/Fraise (Mac OS X)
- 安装了GoClipse 插件的Eclipse ,集成性做得很好;
- Vim/Emacs,万能开发工具;
- LiteIDE,一款专为Go语言开发的集成开发环境。
- 工程管理
- Go命令行工具
- 调试
- FMT 输出日志/gdb
语言
变量
1 | var v1 int |
Go语言也引入了另一个C和C++ 中没有的符号(冒号和等号的组合:=),用于明确表达同时进行变量声明和初始化的工作。
go支持直接交换值
1 | var v10 int |
常量
1 | -12 // 无类型常量 |
go语言的数字类型有: int
、uint
、int32
、int64
、float32
、float64
、complex64
、complex128
1 | const Pi float64 = 3.14159265358979323846 |
类型
- 布尔类型:bool
1 | var v1 bool |
- 整型:int8、byte、int16 、int 、uint、uintptr等。
go语言支持位运算
且有一个特殊类型:uintptr: uintptr is an integer type that is large enough to hold the bit pattern of any pointer.
- 浮点类型:float32、float64。
- 复数类型:complex64、complex128。
1 | var value1 complex64 // 由2 个float32构成的复数类型 |
对于一个复数z = complex(x, y) ,就可以通过Go语言内置函数real(z)获得该复数的实部,也就是x,通过imag(z)获得该复数的虚部,也就是y
- 字符串:string。 Go编译器支持UTF-8 的源代码文件格式
- 字符类型:rune。
- 错误类型:error 。
此外,Go语言也支持以下这些复合类型: * 指针(pointer ) * 数组(array)
1 | [32]byte // 长度为32 的数组,每个元素为一个字节 |
- 切片(slice )
myArray[:5]
- 字典(map)
1 | var myMap map [ string ] PersonInfo |
- 通道(chan ) channel是Go语言在语言级别提供的goroutine 间的通信方式。我们可以使用channel在两个或多个goroutine 之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。
1 | var ch chan int |
- 结构体(struct)
1 | type Rect struct { |
- 接口(interface )
- 流程控制
1 | if a < 5 { |
注意上面的switch语句里面没有break语句
1 | sum := 0 |
且go语言包含goto语句
- 函数
1 | package mymath |
- 不定参数类型
1 | func myfunc(args ... int ) { |
- 闭包
1 | package main |
- defer 解决释放资源的问题, 可以通过defer字段实现资源的自动释放
1 | func CopyFile(dst, src string ) (w int64, err error) { |
- panic()和recover()
panic()函数时,正常的函数执行流程将立即终止,但函数中之前使用defer 关键字延迟执行的语句将正常展开执行,之后该函数将返回到调用函数,并导致逐层向上执行panic流程,直至所属的goroutine 中所有正在执行的函数被终止。
recover()函数用于终止错误处理流程。
面向对象
对于面向对象编程的支持Go 语言设计得非常简洁而优雅。简洁之处在于,Go语言并没有沿袭传统面向对象编程中的诸多概念,比如继承、虚函数、构造函数和析构函数、隐藏的this指针等。优雅之处在于,Go语言对面向对象编程的支持是语言类型系统中的天然组成部分。整个类型系统通过接口串联,浑然一体。我们在本章中将一一解释这些特性。 类型 在Go语言中,你可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法,例如:
1 | type Integer int |
在你需要修改对象的时候,才必须用指针。它不是Go语言的约束,而是一种自然约束。 举个例子:
1 | func (a *Integer) Add(b Integer) { |
- 值语义和引用语义
1 | var a = [3] int {1, 2, 3} |
Go语言中的大多数类型都基于值语义
基本类型
:如byte、int 、bool、float32、float64和string等;
复合类型
:如数组(array)、结构体(struct)和指针(pointer )等。
Go语言中有4个类型比较特别,看起来像引用类型
数组切片
:指向数组(array)的一个区间。
map
:极其常见的数据结构,提供键值查询能力。
channel
:执行体(goroutine )间的通信设施。
接口(interface )
:对一组满足某个契约的类型的抽象。
- 结构体
1 | type Rect struct { |
- 匿名组合:
1 | type Base struct { |
要使某个符号对其他包(package)可见(即可以访问),需要将该符号定义为以大写字母开头
- 接口
非侵入式接口: 将对象实例赋值给接口;将一个接口赋值给另一个接口。
我们定义一个Integer类型的对象实例,怎么将其赋值给LessAdder
1 | type Integer int |
下面的例子,定义了两个不同的包:
1 | package one |
任何实现了one.ReadWriter接口的类,均实现了two.IStream ;
- 任何one.ReadWriter接口对象可赋值给two.IStream ,反之亦然;
- 在任何地方使用one.ReadWriter接口与使用two.IStream 并无差异。
以下这些代码可编译通过:
1 | var file1 two.IStream = new (File) |
接口查询:
1 | if file5, ok := file1.(two.IStream); ok { |
- Any 类型
由于Go语言中任何对象实例都满足空接口interface{},所以 interface{} 看起来像是可 以指向任何对象的Any 类型,如下:
1 | var v1 interface{} = 1 // 将int 类型赋值给interface{} |
fmt包中的Print定义,可以看出any类型的优势。
1 | func Print(a ...interface{}) (n int, err error) |
并发编程
并发编程的模型一般有:
- 多进程
- 多线程
- 基于回调的非阻塞/ 异步IO
- 协程
协程(coroutine)本质上是一种用户态线程,不需要操作系统来进行抢占式调度,且在真正的实现中寄存于线程中,因此,系统开销极小,可以有效提高线程的任务并发性,而避免多线程的缺点。使用协程的优点是编程简单,结构清晰;缺点是需要语言的支持,如果不支持,则需要用户在程序中自行实现调度器。目前,原生支持协程的语言还很少。
子例程(线程)的起始处是惟一的入口点,一旦退出即完成了子程序的执行,子程序的一个实例只会返回一次。 协程可以通过yield来调用其它协程。通过yield方式转移执行权的协程之间不是调用者与被调用者的关系,而是彼此对称、平等的。 协程的起始处是第一个入口点,在协程里,返回点之后是接下来的入口点。
以下是协程的一段伪代码
1 | 生产者协程 |
一个python的例子:
1 | def h(): |
输出结果:
1 | Wen Chuan Fighting! |
Go 语言在语言级别支持轻量级线程,叫goroutine 。 一个函数调用前加上go关键字,这次调用就会在一个新的goroutine 中并发执行。当被调用的函数返回时,这个goroutine 也自动结束了。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。
1 | package main |
代码源文件
1 | package main |
1 | package main |
- channel
channel是Go语言在语言级别提供的goroutine 间的通信方式。我们可以使用channel在两个或多个goroutine 之间传递消息。channel是进程内的通信方式,因此通过channel传递对象的过程和调用函数时的参数传递行为比较一致,比如也可以传递指针等。
channel是类型相关的。也就是说,一个channel只能传递一种类型的值,这个类型需要在声明channel时指定。如果对Unix 管道有所了解的话,就不难理解channel,可以将其认为是一种类型安全的管道。
语法:
1 | var chanName chan ElementType |
在channel的用法中,最常见的包括写入和读出。将一个数据写入(发送)至channel的语法很直观,如下:
ch <- value
向channel写入数据通常会导致程序阻塞,直到有其他goroutine 从这个channel中读取数据。从channel中读取数据的语法是
value := <-ch
如果channel之前没有写入数据,那么从channel中读取数据也会导致程序阻塞,直到channel中被写入数据为止。
1 | Select |
- 缓冲机制
给channel带上缓冲,从而达到消息队列的效果。 要创建一个带缓冲的channel,其实也非常容易:
c := make( chan int , 1024)
从带缓冲的channel中读取数据可以使用与常规非缓冲channel完全一致的方法,但我们也可以使用range关键来实现更为简便的循环读取:
1 | for i := range c { |
需要注意的是,在Go语言中channel本身也是一个原生类型,与map之类的类型地位一样,因此channel本身在定义后也可以通过channel来传递。
- 超时机制
Go语言没有提供直接的超时处理机制,但我们可以利用select机制。
1 | // 首先,我们实现并执行一个匿名的超时等待函数 |
- 单向channel
1 | var ch1 chan int // ch1 是一个正常的channel,不是单向的 |
只有在介绍了单向channel的概念后,读者才会明白类型转换对于channel的意义:就是在单向channel和双向channel之间进行转换。示例如下:
1 | ch4 := make( chan int ) |
关闭channel
close(ch)
关闭后:
x, ok := <-ch
这个用法与map 中的按键获取value的过程比较类似,只需要看第二个bool返回值即可,如果返回值是false 则表示ch已经被关闭。
多核并行化,让出时间片
1 | type Vector []float64 |
- 同步
Go语言包中的sync包提供了两种锁类型:sync.Mutex和sync.RWMutex。 RWMutex相对友好些,是经典的单写多读模型
Go语言提供了一个Once类型来保证全局的唯一性操作,
1 | var a string |
goroutine 和channel 是支撑起Go语言的并发模型的基石,让Go语言在如今集群化与多核化的时代成为一道极为亮丽的风景
最后,看书的过程中,写的关于一些简单的go语言的例子,在这里.