关注点
- go 的协程(routine)是通过调度器,调度,每个调度器绑定到一个线程(thread)上
- 引用类型 channel,map,slice.引用类型的零值是nil(所以全局声明这些变量最好用make eg:var matchers = make(map[string]Matcher))
- 使用接口写通用代码
- _ 空位符,
- (在包中使用init函数和手工调用初始化函数区别 就是 init 自动调用,并且不会)
- 声明
channel 通道
通道是用来在多个goroutine 发送和共享数据,通道分为两种 缓冲通道和无缓冲通道。
缓冲通道 接受和发送不必同步,当通道满了,发送会阻塞,当通道空了接受会阻塞。
无缓冲通道 通道的大小为0,接受和发送必须是同步的,否则就会造成一方阻塞,所以也被称为同步通道。无缓冲通道相当于 make(chan int,0)
总结1 正式因为以上特性,如果在一个goroutine中给无缓冲通道赋值,但没有在其他goroutine 进行读取操作,会造成死锁(一直等待其他协程读取)
eg:
func main(){
messages := make(chan string, 0)
messages <- "cjem" // 没有其他goroutine读取造成死锁
// go func() { messages <- "ping" }()
fmt.Println(<-messages)
}
- 总结2 如果一个缓冲通道,数据被读取结束,但是没有close 通道,那么通道为空,一直在等待数据输入,所以也会造成死锁
queue := make(chan string, 2)
queue <- "1"
queue <- "2"
// 读取结束但是没有close(queue),造成读取方一直等待读取,死锁
for elem := range queue {
fmt.Println(elem)
}
- 总结3 通道用完一定要close,缓冲通道可以保证事件不会阻塞程序运行。
变量声明
如果需要声明初始值为零值的变量应该用var关键字声明变量,如果提供确切的非零值初始化变量或者使用函数返回值创建变量 ,应该使用简明变量声明运算符
指针变量可以在函数之间共享数据
关于方法
- 建议方法的接受者声明为指针。这样既可以防止对象拷贝也可以修改接受者值的状态
- 方法通过接受者类型调用的话无论是通过指针还是值方法都可以调用
- 方法如果通过 实现的接口调用,那么接受者是指针实现的方法 只能在接口类型的值是一个指针的时候被调用(因为可能只&1 这样的值可以复制给接口 但是没有地址)。如果使用值作为接受者声明的方法,在接口类型的值为值或者指针的时候,都可以被调用
接口
如果一个接口只有一个方法,那么这个类型的名字以er结尾
问题
Q1 通道中存储指针而不是值有什么好处?
在通道中只保存指针 而不是值 对于复杂类型可以有效的减少内存占用,如果用值就会造成数值内存拷贝并且等待系统GC
Q2 range channel 在什么情况下自动结束?
close 之后才会结束,如果没有进行close 操作,那么就会造成死锁
go package
使用包来封装不同的语义单元。目的是能够更好的复用代码,并对包内的数据的使用有更好的控制。
同一个目录下所有.go文件必须声明同一个包名。
包名命名惯例是使用包所在目录的名字。
包名及其目录名应该简介,清晰且全是小写的名字。
go get 获取指定包以及包的依赖包
go command
- go vet (vs code 插件可做)校验代码常见错误
- go fmt (vs code 插件可做)格式化代码
- go doc 打印文档(中断查看)
- godoc 网页浏览文档
- godep
- go build -race 检测竞争
数组,切片和映射
数组是切片和映射的基础数据结构
函数之间传递是一个开销很大的操作,在函数之间传递变量总是以值的形式传递,所以如果这个变量是一个数组,意味着整个数组,不管多长,都会被完整复制,并传递给函数。所以函数之间数组传递,应该只穿数组指针。
切片 如果基于这个切片创建新的切片,新的切片会和原有切片共享底层数组。切片是引用类型:
只声明是nil:eg var slice []int
空切片:eg:make([]int,0)
函数中传递切片 值复制,但是切片本身尺寸很小
映射 在函数之间传递映射并不会制造出该映射的副本和切片一样传递map值
切片,map 的分为nil 和空切片(map)
只声明就是nil,make(*,0)或者直接初始化map[string]string{}这样是空切片,但是之后nil才能和nil比较,虽然len()结果一样
类型系统
方法
值接收者:调用时会使用这个值的一个副本执行。
指针接收者
引用类型
切片,映射,通道,接口和函数。永远不要共享一个引用类型的值。赋值传递一个引用类型值的副本,本质上就是在共享底层数据结构。
并发
操作系统会在物理处理器上调度线程来运行,而go 语言的运行时会在逻辑处理器上调度goroutine 来运行。
每个逻辑处理器分别绑定到单个操作系统线程。
并发模式
- 控制程序的生命周期
- 管理可复用的资源池
- 创建可以处理任务的goroutine池
runner
runner 包用来展示使用通道监视程序的执行时间,如果程序运行时间过长,也可以使用runner包来终止程序。(当开发需要调度后台处理任务的时候这种模式很有用)
程序在分配的时间内完成工作,正常终止
程序没有及时完成工作,"自杀"
接受到操作系统发送的中断事件,程序立即试图清理状态,并停止工作。
select 在监控通道的时候 可以添default.可以将监听的通道阻塞变为非阻塞的。
eg:
select {
case err := <-r.interrupt
signal.Stop(r.interruppt)
return true
default:
return false
}
pool 使用缓冲的通道实现资源池,,来管理任意数量的goroutine 之间共享或者独立的资源。
测试(单元测试,基准测试)
单元测试
验证代码正确性
- 正向测试,正常执行的情况下,保证代码不产生错误的测试
- 负向路径 保证代码产生错误,而其实预期错误(构建网络服务,不运行服务,调用服务功能测试)
- 测试函数必须是公开的并且以Test单词开头并且函数的参数必须接受一个指向testing.T 类型的指针,并且不能返回任何值。
- httptest 可以模仿http的网络调用
- 服务端点是指与服务宿主信息无关,用来分辨某个服务的地址,一般是不包含宿主的一个路径。如果在构造网络API,你会希望直接测试自己服务的所有端点,而不用启动整个网络服务。
基准测试
检验代码性能