这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
1 并发编程
1.1 Goroutines
Go语言中的每一个并发执行单元叫做goroutine。一个程序启动时,其主函数在一个单独的goroutine中运行,称为main goroutine。可以使用go语句创建新的goroutine:
go f()
f()会在新创建的goroutine中运行。
1.2 Channels
channels是goroutine之间的通信机制。一个channel可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,即可发送数据的类型。
channel对应一个make创建的底层数据结构的引用。使用make函数创建channel:
ch := make(chan int) // 可以发送int类型数据
上述方式创建的channel是无缓存的,也可以指定第二个整型参数,对应channel的容量。容量大于0为带缓存的channel:
ch = make(chan int, 0) // 无缓存
ch = make(chan int, 2) // 有缓存,缓存大小为2
一个channel有发送和接收两个主要操作,都是通信行为,都使用<-运算符。在发送语句中,<-分割channel和要发送的值。在接收语句中,<-写在channel对象之前,一个不使用接收结果的接收操作也是合法的。
ch <- x // 发送
x = <-ch // 接收
<-ch // 接收
使用close(ch)可以关闭channel,随后的任何发送操作将导致panic异常。对一个已经close的channel进行接收操作依然可以接收到之前已成功发送的数据,若channel中已无数据,将会产生一个零值的数据。
无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作,当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句。反之,如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作。
带缓存的Channel内部持有一个元素队列。向缓存Channel的发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素。如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个goroutine执行接收操作而释放了新的队列空间。相反,如果channel是空的,接收操作将阻塞直到有另一个goroutine执行发送操作而向队列插入元素。
1.3 Lock
var lock sync.Mutex // 互斥锁
// 获取互斥锁,如果其他goroutine已经获得该锁,
// 则会阻塞该操作直到其他goroutine调用Unlock释放锁
lock.Lock()
lock.Unlock() // 释放锁
1.4 WaitGroup
// 计数器,goroutine启动+1,退出-1
var wg sync.WaitGroup
wg.Add(delta int) // 计数器+delta
wg.Done() // 计数器减1
wg.Wait() // 阻塞直到计数器为0
2 依赖管理
三要素:
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
3 单元测试
3.1 规则
- 所有测试文件以"_test.go"为后缀名
- 测试函数以"Test"为前缀名,后缀名以大写字母开头
func TestName(t *testing.T) {
// t参数用于报告测试失败和附加的日志信息
}
- 初始化逻辑放到TestMain中
3.2 测试覆盖率
对待测试程序执行的测试程度称为测试覆盖率。实际项目中的覆盖率一般为50%~60%。进行单元测试时,测试分支要相互独立,全面覆盖,测试单元粒度足够小,符合函数设计的单一职责。
3.3 基准测试
基准测试是测量一个程序在固定工作负载下的性能。