Go 工程进阶 | 青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
一、并发
-
概念
(1)并发:多线程程序在一个核的 CPU 上运行
(2)并行:多线程程序在多个核的 CPU 上运行
-
协程Goroutine
协程处于用户态,是轻量级的线程(一个线程可以跑多个协程),栈空间少(KB级别,线程是MB级别)
代码主干:开启多个线程
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
测试结果:
-
通道Channel
(1)无缓冲通道:make(chan int)
(2)有缓冲通道:make(chan int,2)
代码主干:生成 0-9 之间的整数并计算他们的平方
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
代码主干:计数10000
var ( x int64 lock sync.Mutex ) func addWithLock() { for i := 0; i < 2000; i++ { lock.Lock() x += 1 lock.Unlock() } } func addWithoutLock() { for i := 0; i < 2000; i++ { x += 1 } } func Add() { x = 0 for i := 0; i < 5; i++ { go addWithoutLock() } time.Sleep(time.Second) println("WithoutLock:", x) x = 0 for i := 0; i < 5; i++ { go addWithLock() } time.Sleep(time.Second) println("WithLock:", x) }代码测试:
(自己测试为什么都是10000)fo了
-
线程同步WaitGroup
计数器开启协程 +1,执行结束 -1,主协程阻塞直到计数器为 0。
代码主干:
func ManyGoWait() { var wg sync.WaitGroup wg.Add(5) for i := 0; i < 5; i++ { go func(j int) { defer wg.Done() hello(j) }(i) } wg.Wait() }代码测试:
二、依赖管理
1. Go 依赖管理演进
1. GOPATH
-
GOPATH 是 Go 语言支持的一个环境变量,value 是 Go 项目的工作区。目录有以下结构:
- src:存放 Go 项目的源码;
- pkg:存放编译的中间产物,加快编译速度;
- bin:存放 Go 项目编译生成的二进制文件。
-
弊端:
同一个 pkg,有 2 个版本 , A -> A(),B-> B(),而 src 下只能有一个版本存在,AB 项目无法保证都能泽通过。也就是在 gopath 管理模式下,如果多个项目依赖同一个库,则依赖该库是同一份代码,所以不同项目不能依赖同一个库的不同版本,这很显然不能足我们的项目依赖需求。为了解决这问题,govender 出现了。
2. Go Vendor
项目目录下增加 vendor 文件,所有依赖包副本形式放在 $ProjectRoot/vendor,在 vendor 机制下,如果当前项目存在 vendor 目录,会优先使库该目录下的依赖,如果依赖不存在,会从 GOPATH 中寻找。
弊端:
- 无法控制依赖的版本。
- 更新项目可能出现依赖冲突,导致编译出错 。
3. Go Module
- 通过 go.mod 文件管理依赖包版本
- 通过 go get/go mod 指令工具管理依赖包
2. 依赖管理三要素
1. 配置文件,描述依赖(go.mod)
indirect 后缀表示 go.mod 对应的当前模块,没有直接导入该依赖模块的包,也就是非直接依赖,标示间接依赖。
incompatible 后缀表示主版本 2+ 模块会在块路径增加 /vN 后缀,这能让 go module 按照不同的模块来处理同一个顶目不同主版本的依赖。由于 go module 是过往实验性引入,所以这顶规则提出之前已经有一些仓库打上了 2 或者更高版本的 tag 了,为了兼容这部分仓库,对于没有 go.mod 文件并且主版本在 2 或者以上的依赖,会在版本号后加上 +incompatible 后缀。
2. 中心仓库管理依赖库(proxy)
直接使用版管理仓库下载依赖,存在多个问题:
-
无法保证构建确定性:软件作者可以直接在代码平台增加/修改/删除软件版本,导致下次构建使用另外版本的依赖,或者找不到依赖版本。
-
无法保证依赖可用性:依赖软件作者可以直接在代码平台删除软件,导致依赖不可用:大幅增加第三方代码托管平台
go proxy 就是解决这些问题的方案,Go Proxy 是一个服务站,它会缓存源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用,从而实现了供 "immutability" 和 "available" 的依赖分发,使用 Go Proxy 之后,构建时会直接从 Go Proxy 站点拉取依赖 。
3. 本地工具(go get / mod)
-
go get
-
go mod
三、单元测试
1. 单元测试概念和规则
1. 单元测试在开发流程所处的位置如图:
2. 规则:
-
所有测试文件以 _test.go 结尾
-
测试函数的名称为 func TestXxx(*testing.T)
-
初始化逻辑放到 TestMain 中
2. Mock测试
快速 Mock 函数:
- 为一个函数打桩
- 为一个方法打桩
对 ReadFirstLine 打桩测试,不再依赖本地文件
3. 基准测试
1. 例子
2. 运行
3. 优化
介绍了 Go 一些和工程实践密切相关的介绍和操作,包括并发,依赖和测试。
在工程项目的进行中,并发需要事先构建事件在时间上的运行逻辑,然后根据这些逻辑安排代码的结构,避免一些代码的冗余;依赖经过多种方式的演变,在可用性上有着更加方便的体验;测试是一个比较繁琐的过程,有时会耗费一半以上的项目时间进行测试,从而保证项目指标达到预期。