这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。
1.语言进阶
1.1 并发 VS 并行
并发:多线程程序在一个核的CPU上运行
Go可以充分发挥多核优势,高效运行
1.2 Goroutine
协程:用户态,轻量级线程,栈MB级别
线程:内核态,线程跑多个协程,栈KB级别
1.3 CSP(Communicating Sequential Process)
Go提倡通过通信共享内存而不是通过共享内存实现通信
1.4 Channel
make(chan 元素类型,[缓冲大小])
- 无缓冲通道:make(chan int)
- 有缓冲通道:make(chan int,2)
区别:类似于生产与消费,无缓冲通道边生产边消费,有可能出现断货情况;而有缓冲通道把生产的产品囤积一部分,能尽量避免供不应求的情况。
1.5 并发安全 Lock
在多线程并发中可能会出现信息流失,导致安全问题,为避免这种情况,需对信息进行加锁操作
例:对变量执行2000次+1操作,5个协程并发执行,预期结果为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)
}
/*
WithoutLock: 8576
WithLock: 10000
*/
可以看到不加锁的输出结果不一定为预期结果,说明不加锁存在信息流失
1.6 WaitGroup
计数器:开启协程+delta(Add(delta) );执行结束-1(Done() );主协程阻塞直到计数器为0(Wait() )
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
var wg sync.WaitGroup
wg.Add(5) //delta=5
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done() //delta-1
hello(j)
}(i)
}
wg.Wait() //delta=0终止
}
/*
hello goroutine : 4
hello goroutine : 2
hello goroutine : 3
hello goroutine : 1
hello goroutine : 0
*/
2.依赖管理
2.1 背景
Go依赖管理演进:GOPATH -> GO Vender -> Go Module
GoModule:
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod指令工具管理依赖包
最终目标:定义版本规则和管理项目依赖关系
2.2 依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
2.3 依赖配置
go.mod
依赖标识:[Module Path] [Version/Pseudo-version]
version
-
语义化版本:
- V1.3.0;V2.3.0
-
基于commit伪版本:vX.0.0-yyyymmddhhmmss-abcdefgh1234
- v0.0.0-20220401081311-c38fb59326b7
indirect
间接依赖:A->B->C,A到B是直接依赖,A到C是间接依赖
incompatible
- 主版本2+模块会在模块路径增加/vN后缀
- 对于没有go.mod文件并且主版本2+的依赖,会+incompatible
2.4 依赖分发
回源
存在问题:
- 无法保证构建稳健性(增加/修改/删除软件版本)
- 无法保证依赖可用性(删除软件)
- 增加第三方压力(代码托管平台负载问题)
Proxy
变量 GOPROXY
Proxy 1 --> Proxy 2 --> Direct(源站)
前面站点都没有依赖的话会回源到第三方代码平台上
go get
go get example.org/pkg
- @update 默认
- @none 删除依赖
- @v1.1.2 tag版本,语义版本
- @23dfdd5 特定的commit
- @master 分支的最新commit
go mod
- init 初始化,创建go.mod文件
- download 下载模块到本地缓存
- tidy 增加需要的依赖,删除不需要的依赖
3.测试
回归测试 --> 集成测试 --> 单元测试 (覆盖率逐层变大,成本逐层降低)
3.1 单元测试
通过单元测试得到的输出值与期望值进行校对。可以保证质量、提升效率
规则
-
所有测试文件以 _test.go结尾
-
func TestXxx(*testing.T)
func TestPublishPost(t *testing.T){} -
初始化逻辑放到TestMain中
func TestMain(m *testing.M) { //测试前:数据装载、配置初始化等前置工作 code := m.Run() //测试后:释放资源等收尾工作 os.Exit(code) }
覆盖率
编写的代码中被成功执行的代码所占的比例,可以用来判断代码编写的一系列功能是否都能成功运行,是否存在有些功能不能运行而产生bug等。
- 一般覆盖率:50%-60%,较高覆盖率%80+
- 测试分支相互独立、全面覆盖
- 测试单元粒度足够小,函数单一职责
3.2 依赖
外部依赖 => 稳定&幂等
3.3 Mock
monkey:github.com/bouk/monkey
快速Mock函数:
- 为一个函数打桩
- 为一个方法打桩
3.4 基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力