语言进阶
- 并发与并行
- 并发是多个线程在一个CPU核上运行
- 并行是多个线程在多个CPU核上运行
- Goroutine
- 线程
- 内核态,栈内存是MB级别
- 协程
- 用户态,栈内存是KB级别
- go语言一次可以创建上万数量的协程
- 怎样开启?在调用函数时,前面加
go关键字
- 线程
- CSP (Commnuicating Sequential Processes)
- 提倡通过通信共享内存,而不是通过共享内存来实现通信
- Channel
-
创建
make(chan 元素类型[, 缓冲大小]) // 无缓冲通道 make(chan int) // 有缓冲通道 make(chan int, 2) -
无缓冲通道和有缓冲通道的区别
- 无缓冲通道发送和调用会是同步的,也被称为同步通道
-
- 并发安全Lock
lock sync.Mutex声明一个锁 通过临界区来实现的lock.Lock()加锁lock.Unlock()释放锁
- sync.WaitGroup(有点类似于Java的Barrier)
Add(delta int)计数器+deltaWait()阻塞直到计算器为0Done()计数器-1- 用WaitGroup实现一个计数器
- 创建WaitGroup
- WaitGroup调用
Add(1) - 在需要阻塞的地方调用
Wait() - 在完成任务后调用WaitGroup的
Done()
依赖管理
- 为什么要有依赖管理
- 工程不可能只基于标准库从零开始编写搭建
- 可以使用优秀的第三方Go包
- Go依赖管理的演进
- GOPATH
- Go Vendor
- Go Module
- 依赖关系
- 不同环境(项目)的版本不同
- 控制依赖库的版本
- GOPATH
- bin 项目编译的二进制文件
- pkg 项目编译的中间产物,加速编译
- src 项目源码
- 代码直接依赖src下的代码
- 无法实现多版本的依赖控制
- go get会把包下载到src目录下
- GoVendor
- 在项目目录下生成了一个vendor文件
- 所有的依赖都以副本的形式放在
$ProjectRoot/vendor- 解决了多版本的依赖控制问题
- 依赖的寻址方式:先vendor 后GOPATH
- 同项目中无法解决不兼容的版本依赖
- Go Module
- 新版本默认开启,目前最推荐的依赖管理方式
- 通过
go.mod文件管理依赖包 - 通过
go get/go mod指定工具管理依赖包 - 依赖管理三要素
-
- 配置文件,描述依赖
go.mod
- 配置文件,描述依赖
-
- 中心仓库管理依赖库
Proxy
- 中心仓库管理依赖库
-
- 本地工具
go get/mod
- 本地工具
-
- 依赖标识 [Moudule Path][Version/Pseudo-version]
- go.mod格式
-
module xxx/xxx/xxx 声明模块,依赖管理的基本单元
-
go 1.16 声明标准库的版本
-
require
require( xxx/xxx v1.1.2 xxx/xxx v1.0.0 // indirect xxx/xxx v3.3+incompatible ) -
版本号
- 语义化版本
${MAJOR}.${MINOR}.${PATCH}- MAJOR大版本,不兼容
- MINOR小版本,兼容
- PATCH问题修复
- 基于commit的伪版本
v0.0.0-yyyyymmddhhmmss-commitid
- 语义化版本
-
indirect 声明间接依赖
-
incompatible 标识可能出现不兼容的依赖
-
- 间接依赖不同的版本时,会选择最低的兼容版本
- 依赖分发
- 使用源代码依赖会产生问题
- 无法保证构建的稳定性
- 无法保证依赖可用性
- 增加第三方压力
- Proxy因此而产生
- 稳定,可靠
- 依赖都在Proxy中拉取
- 如何配置
- 通过GOPROXY环境变量
- 是一个用,分割的URL列表,最后一个是direct表示源站
- 使用源代码依赖会产生问题
go getgo get example.org/pkg- 加
@update默认 - 加
@none删除依赖 - 加
@v1.1.2指定tab版本 - 加
@commitid指定特定的commitid - 加
@master指定分支,拉取该分支最新的commit
- 加
go mod- init 初始化,创建go.mod文件
- download 下载依赖模块到本地缓存
- tidy 增加需要的依赖,删除不需要的依赖,自动化整理依赖
测试
测试是避免事故的最后一首屏障
- 测试的划分
- 回归测试 手动回归一些固定的使用场景
- 集成测试 系统功能维度做验证
- 单元测试 开发对开发的模块做功能验证
- 单元测试
- 组成:输入、输出、输出期望值
- 保证质量
- 提升整体效率,缩短定位问题+修改的周期
- 规则
- 文件以
_test.go结尾 - 函数签名为
func Testxxxx(t *testing.T) - 初始化逻辑写在
func TestMain(m *testing.M)中- 前置初始化
code := m.Run()- 后置收尾
- 文件以
- 执行测试
go test [flag] [packages]
- 可以引用一些assert包
- 单元测试的评估
- 代码覆盖率
go test xxxx.go xxx.go ... --cover输出代码覆盖率- 实际项目中一般要求:50%~60%,要求严格的场景下可以80%
- 如何提升覆盖率
- 测试分支相互独立,全面覆盖
- 测试单元粒度足够小,函数单一职责
- 代码覆盖率
- Mock测试
-
monkey
https://github.com/bouk/monkey
-
打桩,测试时用一个函数替换另一个函数
monkey.Patch(funcName, func()string{}) defer monkey.Unpatch(funcName)- 可以解除对本地文件的依赖
-
- 基准测试
-
需求:优化代码,需要对当前代码分析
-
和单元测试的规则是一致的
-
同步写法
func Benchmarkxxxxx(b *testing.B){ b.ResetTimer() } -
异步写法
func BenchmarkxxxxParallel(b *testing.B){ b.ResetTimer() b.RunParallel(func(pb *testing.PB){ xxx }) } -
执行基准测试
go test -bench=.
-