这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
一、本堂课重点内容:
- 并发编程
- 依赖管理
- 单元测试
- 项目实战
二、详细知识点介绍:
- Go语言并发的一大特点是使用协程 Goroutine,其特色在于处于用户态,开销低,相当于一个轻量级的线程。Go语言可以通过在函数前加go命令来轻松创建协程。
func closure1() {
for i := 0; i < 3; i++ {
go func(j int) {
println(j)
}(i)
}
-
Go提倡通过通信来共享内存(使用Channel,而非临界区)。下面是Channel的创建(make)和通信(<-)用法。
make(chan 元素类型,[缓冲大小]) //Channel的创建方式
//一小段Channel实例
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
-
并发安全 Lock。 使用
var 变量名 sync.mutex创建锁,使用锁名.lock()和锁名.unlock()来加锁、解锁。 -
WaitGroup。为了保证主程序等待协程运行完毕再结束,我们先前使用sleep来实现。然而这种方式是不优雅的,使用WaitGroup可以更好地解决这一问题。
其本质是维护一个计数器。Add初始化,Done减一,Wait到计数器为0时结束。
func ManyGo() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
-
go.mod 类似于 Maven ,用于管理依赖版本。
-
测试。测试按成本从小到大有单元测试、集成测试、回归测试。其中成本与覆盖率成反比,就是说成本越低的覆盖率却越高。因此单元测试十分重要。
单元测试有几点:
-
- 所有的测试文件以
_test.go结尾。
- 所有的测试文件以
-
- 测试文件中的测试函数形式为
func TestXxx(t *testing.T) {}。
- 测试文件中的测试函数形式为
-
- 初始化逻辑放在 TestMain 中。测试从TestMain进入,依次执行测试用例,最后从TestMain退出。
-
func TestMain(m *testing.M) {
//测试前工作
code := m.run()
//测试后工作
os.Exit(code)
}
- 单元测试覆盖率。要查看单元测试覆盖率需要在
go test 文件名..后加上--cover。 - 单元测试 - Mock 。即打桩,用B函数替换A函数,完事后再换回原A函数(一般defer)。
- 基准测试。可以测试性能,用于优化代码,Go 内置有基准测试框架。测试函数形式为
func BenchmarkXxx(b *testing.B) {}。
三、课后个人总结:
实战项目外主要遇到几个问题:
func closure() {
for i := 0; i < 3; i++ {
go func() {
println(i)
}()
}
time.Sleep(3 * time.Second)
}
func closure1() {
for i := 0; i < 3; i++ {
go func(j int) {
println(j)
}(i)
}
time.Sleep(3 * time.Second)
}
上面两个函数乍一看相同,实际上第一个结果错误,第二个正确。原因在于第一个直接调用了外部循环变量,主函数循环很快结束了,而协程才开始打印,因此与预期结果不同。第二个例子用闭包解决了这一问题。
-
一些小问题:
-
- os打开的文件需要关闭,部分流需要关闭,tcp连接需要关闭,bufio不需要关闭。
-
go test、go run命令后如有多个文件名需要把每个文件名的前缀都写清楚。
-