Go语言进阶|青训营笔记
这是我参与「第五届青训营」伴学笔记创作活动的第2天。
一、本堂课重点内容
- Go语言的进阶:并行处理、Goroutine,channel
- 并发锁
- 依赖管理:go.mod
- 测试
二、详细知识点介绍
1. 语言进阶
1.1 并发 vs 并行
- 并发:多线程程序在单核cpu运行
- 并行:多线程程序在多核cpu运行
- go可以发挥多核优势并行,利用调度充分利用资源
1.2 Goroutine
- 协程:用户态、轻量级线程、栈MB级别
- go语言一次可创建上万协程
- 线程:内核态、线程跑多个协程,栈KB级别
- 使用:在调用函数时前面加go关键字
- 为函数创建协程运行
1.3 communicating sequential processes
- 提倡通过通信共享内存,而不是通过共享内存实现通信
- 没有临界区 而是使用通道
- Channel通道
- make(chan 元素类型, [缓冲大小])
- 无缓冲通道:make(chan int)
- 有缓冲通道:make(chan int,2)
- 生产->消费:并发安全,能够保证执行的顺序
- 消费者消费速度慢,可以使用带缓冲channel解决生产者和消费者的速度不一致的问题
1.4 并发安全锁
- 使用lock保证并发安全
lock sync.Mutex //保障并发安全性 lock.Lock() xxx lock.Unlock()
1.5 WaitGroup
- 计数器
- 开启协程+1 Add(delta int)
- 执行结束-1 Done()
- 主协程阻塞直到计数器为0 Wait()
2.依赖管理
- 配置文件描述依赖 go.mod
- 中心仓库管理依赖库 proxy
- 本地工具 go get/mod
2.1 背景
- 单体函数组成复杂项目
- 工程项目不能基于标准库
- 管理依赖库
2.2 Go依赖管理的演进
- 不同环境依赖的版本不同
- 控制依赖库的版本
- GOPATH:
- 环境变量$GOPATH
- bin, pkg, src
- 项目代码直接依赖src下的代码
- 通过go get下载最新版本的包到src目录下
- 弊端:若A B项目依赖同一package的不同版本,则无法实现package的多版本控制
- 环境变量$GOPATH
- GO Vendor
- 项目目录增加vendor文件,依赖包以副本形式放在$ProjectRoot/vendor
- 依赖寻址方式:vendor -> GOPATH
- 通过每个项目引入一个依赖的副本解决多个项目需要同一个package依赖的冲突问题
- 弊端:无法控制依赖的版本,更新项目可能导致依赖冲突从而编译出错
- GO Module
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod指令工具管理依赖包
- 定义版本规则和惯例项目依赖关系
- GOPATH:
- 依赖管理三要素
- 配置文件描述依赖的包 go.mod
- 中心仓库管理依赖库 proxy
- 本地工具 go get/mod
2.3 依赖配置 - go.mod
- 类似maven
//依赖管理基本单元 module xxx/project/app //原生库 go 1.16 //单元依赖 require( xxx/lib1 v1.0.2 xxx/lib2 v1.0.0 //indirect ... ) - 依赖标识: [Module Path][Version/Pseudo-version]
- version
- 语义化版本:v 1.1.1
- 基于commit的伪版本:v X.0.0-yyyymmddhhmmss-xfhdfgewuif
- indirect
- 间接依赖标识,没有直接导入的
- incompatible
- 主版本2+后的模块会在模块路径增加vN后缀
- 对于没有go.mod文件并且主版本2+后的依赖会+incompatible
- 若两项目分别依赖一个项目的两个不同版本,则在编译时,会选择最低的兼容版本
2.4 依赖分发 - 回源
- 寄存代码在第三方托管平台(github)问题
- 无法保证构建稳定性
- 可以在代码平台进行增删改版本
- 在下次使用依赖时发生问题
- 无法保证依赖可用性
- 增加第三方压力
- 代码托管平台的负载问题
- 无法保证构建稳定性
- Proxy
- 缓存原版本的依赖内容
- 若源平台修改删除相关依赖,可以从Proxy获得相关内容
- GOPROXY
2.5 go get工具
- go get example.org/pkg + 下列tag
- @update 默认
- @none 删除依赖
- @v1.1.2 tag版本
- @23dff5 拉取特定commit版本
- @master 分支最新commit
2.6 go mod工具
- go mod + 下列tag
- init 初始化,创建go.mod文件
- download 下载模块到本地缓存
- tidy 增加需要的依赖,删除不需要的依赖
3. 测试
- 回归测试:手动通过终端进行测试
- 集成测试:系统功能维度进行测试
- 单元测试:测试开发阶段对于单独模块进行测试验证
- 覆盖率逐渐变大,成本降低
3.1 单元测试 Unit test
- go test xxx_test.go xxx.go --cover
- 组成部分
- 输入-测试单元-输出
- 与期望进行校对
- 规则
- 所有测试文件以_test.go结尾
- func TestXxx(*testing.T)
- 初始化逻辑放到TestMain中
- code:= m.run()
- 单元测试assert
- assert.Equal
- 修改对应位置
- 覆盖率
- 衡量代码经过了足够的测试,评估项目的测试水准
- 提升代码测试覆盖率,测试更多行的代码
- 一般项目覆盖率 50-60%,较高为80%+
- 测试分支相互独立,全面覆盖
- 测试单元粒度足够小,函数单一职责
3.2 测试依赖
- 外部依赖 -> 稳定&幂等
3.3 文件处理测试
- 测试依赖及本地文件
3.4 Mock 为一个函数/方法打桩
- 包装原函数,replace为另一个函数,unpatch卸载
- 打桩测试不再依赖本地文件
- 保证测试可以在任何环境进行执行
3.5 基准测试
- 优化代码,需要对代码分析
- 内置测试框架支持对代码的分析
- BenchmarkXxx(* testing.B)
- 同时测试Parallel情景下的性能
- 对比后发现性能偏差,进行优化
三、实践练习例子
- 需求:社区话题页面
- 展示话题(标题、文字描述)、回帖
- 本地web服务
- 话题和回帖使用文件存储
- 分层结构
- 数据层:数据Model,外部数据增删改查
- 逻辑层:业务Entity,处理核心业务逻辑输出
- 视图层:视图view,处理和外部的交互逻辑
- 代码
四、课后个人总结
本堂课主要介绍了并行编程的相关内容,介绍了go语言定义依赖的相关信息,以及测试的内容。在实践例子中对分层实现项目进行了简单介绍。本堂课主要的难点在于并行的实现以及并行锁的相关使用。