Go 语言进阶 | 青训营笔记

75 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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 基准测试

基准测试是测量一个程序在固定工作负载下的性能。

参考资料

[Go语言圣经(中文版)]