这是我参与「第五届青训营 」伴学笔记创作活动的第2天。
本节课主要学习的是Go语言的工程实践内容
分为四个部分:
- 语言进阶
- 依赖管理
- 测试
- 项目实战
1.语言进阶
1.1 并发设计
并发与并行的区别:
并发是多线程程序在一个核的CPU上运行
并行是多线程程序在多个核的CPU上运行
Go语言可以充分发挥多核优势,实现高效运行
1.2 Goroutine
概念:协程
协程:用户态,属于轻量级线程,栈级别MB
对比线程:线程是内核态,一个线程跑多个协程,栈级别KB
怎么开启协程(一个简单的例子):
import (
"fmt"
"time"
)
func hello(i int) {
println("hello goroutine:" + fmt.Sprint(i))
}
func helloroutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
CSP(Communicating Sequential Processes):
Go中提倡通过通信共享内存,而不是通过共享内存而实现通信
Channel
通道是一种引用类型 需要用make进行创建 make(chan 元素类型,[缓冲大小])
无缓冲通道 示例:make(chan int)
有缓存通道 示例: make(chan int,2)
通道容量代表通道能存储多少个数。
package main
func main() {
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)
}
}
Sync包:
并发安全Lock
并发不加锁,在对共享内存进行操作时可能会出现问题
WaitGroup:
WaitGroup中暴露了三个方法,Add() \ Done() \ Wait()
计数器 开启协程+1,执行结束-1;主协程阻塞直至计数器为0。
2.依赖管理
Go依赖管理的演进
GOPATH->Go Vender->Go Module
不同环境依赖的版本不同;
控制依赖库的版本;
2.1.1 GOPATH
GOPATH项目代码都依赖于src下的代码,因此存在一个问题:
当后续版本更新后,前面版本的代码可能无法创建成功,即无法实现package的多版本兼容。
2.1.2 GO Vendor
在项目目录下增加Vendor文件,所有依赖包副本都放在vendor文件
在查询依赖时首先去vendor中查找,没有找到再查找gopath路径,这样就解决了多个项目需要同一个package依赖的问题。
Vendor的弊端:
Vendor无法控制以来的版本,当更新项目后可能出现依赖冲突,从而导致编译出错。
2.1.3 GO Module
通过go.mod文件管理依赖包版本
通过go get/go mod指令工具管理依赖包
2.2 依赖管理三要素
-
配置文件,描述依赖 go.mod
-
中心仓库管理依赖 proxy
-
本地工具 go get/mod
2.3 依赖配置
配置Go.mod
依赖配置 -version
语义化版本:
{MINOR}.${PATCH}
V1.3.0
基于commit伪版本:
Vx.0.0-yyyymmddhhmmss-abcdefgh1234
例子:
V0.0.0-20220401081311-c38fb59326b7
上述的indirect关键字:对于没有直接导入的依赖,用Indirect关键字标识。
Incompatible
对于没有go.mod文件且主版本在V2以上的依赖,会加Incompatible后缀。
依赖分发-回源
依赖可以去哪里下载:github、SVN等
问题:
- 无法保证构建稳定性;
- 无法保证依赖可用性;
- 增加第三方压力;
Proxy:
在Github、SVN后加一层Proxy,可以解决上述问题。
变量GOPROXY
GOPROXY=PROXY1,PROXY2,Direct
工具 Go get
Go get
- @update 最新版本
- @none 删除
- @v1.2.2 指定版本
- @34dfdd5 特定commit
- @master 分支最新的commit
工具 Go mod
Init 初始化,创建go.mod文件
Download 下载模块到本地缓存
Tidy 增加需要的依赖,删除不需要的依赖
3.go 测试内容
开发->测试->上线
测试分为三种:回归测试、集成测试、单元测试
从上到下,覆盖率逐层变大,成本逐层降低。
单元测试覆盖率一定程度决定了代码的质量。
输入->测试单元->输出,利用输出与期望值进行校对,来实现验证。
意义:通过单元测试可以保证质量同时提升效率。
-
单元测试-规则:
所有测试文件都已_test.go结尾
命名 func Testxxx(*tesing.T)
初始化逻辑都放到TestMain中
-
代码覆盖率:利用代码覆盖率来评估代码的测试等级。
对各代码分支进行测试。
一般的覆盖率:50%-60%,较高覆盖率80%+。
测试分支相对独立、全面覆盖。
测试单元粒度要求足够小,函数单一职责。
-
单元测试-依赖
外部依赖 => 稳定且幂等
-
单元测试-Mock
意义:不依赖本地文件,在任何场景下都能实现测试函数。
-
基准测试:
命名 Benchmark(b *tesing.B)
基准测试支持并行
基准测试的优化 :fastrand(),提升性能,但是牺牲了随机数列一致性。
总结
在本节课中我主要学习了Golang语言的进阶语法知识,以及学习了Go中的并发处理方式和测试相关内容,包括单元测试以及基准测试的测试方法,同时在课上老师还带着我们进行了一个简单项目的设计。课后我会将老师上课讲的代码实际运行一下,同时完成上课时留下的课后练习,然后预习下节课高质量编程的相关知识。