Go 语言进阶与依赖管理 | 青训营笔记
这是我参与「第五届青训营」伴学笔记创作活动的第 2 天
一、本堂课重点内容
- 并发和并行
- goroutine
- channel
- 单测
二、详细知识点介绍
并发和并行
- 并发:多线程程序在 一个核 的 cpu 上运行
- 并行:多线程程序在 多核 cpu 上运行
GO 实现并发的方式
实现并发的过程中最重要的是要解决两个问题:
- 如何让多个任务并发执行
- 如何让多个任务之间进行通信
go 实现任务并发执行通过 协程(goroutine) 来实现,实现任务之间通信通过 通道(channel) 来实现。
Goroutine
go 在并发上有着其他语义难以企及的优势。这是因为 go 语言的并发是基于协程(Goroutine)的,而不是基于线程的。
Goroutine 是 go 语言的并发体,它是一种比线程更轻量级的存在。它的调度是由 go 运行时进行管理的。它的调度是由 go 运行时进行管理的。我们一般把 Goroutine 称为 协程。
go 中的协程在 用户态 中进行,而不是内核态,所以协程的切换不需要内核态的切换,所以协程的切换比线程的切换要快。协程的切换是由 用户自己控制 的,而线程的切换是由操作系统控制的。
Channel
go 不是通过共享内存通信,而是通过通信共享内存。通道是一种特殊的类型,它可以让我们通过它来传递数据。通道是一种 引用类型,通道本身是没有数据的,它需要通过 make 函数来初始化才能使用。
并发安全 Lock
通过加锁可以保证并发数据安全
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
}
}
WaitGroup
控制协程进行任务编排
- Add 方法用于设置 WaitGroup 的计数值,可以理解为子任务的数量
- Done 方法用于将 WaitGroup 的计数值减一,可以理解为完成一个子任务
- Wait 方法用于阻塞调用者,直到 WaitGroup 的计数值为 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()
}
依赖管理
GOPATH
无法实现 package 的多版本控制
Vender
更新项目又可能出现依赖冲突,导致编译出错。
Module
依赖三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库
- 本地工具 go get/mod
通过 go.mod 文件管理依赖包版本
go mod:打包模块go get:下载最新版的包到 src 目录下
依赖配置
- version:版本号
- indirect:间接依赖
- incompatible:可能不兼容
依赖分发
- 无法保证构建稳定性:增加/修改/删除软件版本
- 无法保证依赖可用性:删除软件
- 增加第三方压力:代码托管平台负载问题
go get
go get example.org/pkg
- @update:最新版本,默认
- @none:删除依赖
- @v1.0.0:tag 版本,语义版本
- @23dfdd5:commit 版本
- @master:分支版本
go mod
- go mod init:初始化 go.mod 文件
- go mod download:下载依赖
- go mod tidy:添加需要的依赖,删除不需要的依赖
单元测试
回归测试:软件测试类型,以确认新的程序或代码更改未对现有功能产生影响。 集成测试:软件测试类型,用于验证软件组件之间的接口是否正确。 单元测试:软件测试类型,用于验证软件组件是否正确。
- 测试文件以
_test.go结尾 - 测试函数以
Test开头 - 初始化逻辑使用
TestMain函数
func TestMain(m *testing.M) {
// 测试前:数据装载,配置初始化等
code := m.Run()
// 测试后:资源释放,数据清理等
os.Exit(code)
}
单元测试的优点
- 便于后期重构。单元测试可以为代码的重构提供保障,只要重构代码之后单元测试全部运行通过,那么在很大程度上表示这次重构没有引入新的 BUG,当然这是建立在完整、有效的单元测试覆盖率的基础上。
- 优化设计。编写单元测试将使用户从调用者的角度观察、思考,特别是使用 TDD 驱动开发的开发方式,会让使用者把程序设计成易于调用和可测试,并且解除软件中的耦合。
- 文档记录。单元测试就是一种无价的文档,它是展示函数或类如何使用的最佳文档,这份文档是可编译、可运行的、并且它保持最新,永远与代码同步。
- 具有回归性。自动化的单元测试避免了代码出现回归,编写完成之后,可以随时随地地快速运行测试,而不是将代码部署到设备之后,然后再手动地覆盖各种执行路径,这样的行为效率低下,浪费时间。
优化单元测试
- 测试分支回想独立,避免测试分支之间的耦合。
- 测试单元粒度足够小,函数单一职责
测试依赖-Mock
打桩
- 幂等性:同样的操作,多次执行结果相同。
- 稳定性:测试依赖的服务稳定性,如果依赖的服务不稳定,会导致测试失败。
基准测试
基准测试:软件测试类型,用于验证软件组件的性能。
- Benchmark 开头:基准测试函数
- Benchmark + Parallel:并行基准测试函数
rand 函数有全局锁,导致并行基准测试函数性能下降。 fastRand 函数没有全局锁,性能提升。
四、课后个人总结
- go 实现任务并发执行通过 协程(goroutine) 来实现,实现任务之间通信通过 通道(channel) 来实现。
- 并行安全 Lock 和 WaitGroup 控制协程
- 依赖管理对于项目后期维护和扩展性非常重要
- 单元测试和基准测试是保证代码质量的重要手段