这是我参与「第三届青训营 -后端场」笔记创作活动的的第二篇笔记
语言进阶
从并发编程的角度了解Go高性能的本质
并发VS并行
并发:多线程程序在一个核的CPU上运行
并行:多线程程序在多个核的CPU上运行
Goroutine
协程:用户态,轻量级线程,栈KB级别
线程:内核态,线程跑多个协程,栈MB级别
快速打印 hello goroutine
func hello(int i){
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
CSP Communicating Sequential Processes
让一个Goroutine发送一个特定的值到另一个Goroutine的机制
通过共享内存实现通信必须用互斥量进行加锁,一定程度上容易影响程序性能
Channel
创建方法:make(chan 元素类型, [缓冲大小])
无缓冲通道 make(chan int)
有缓冲通道 make(chan int,2) 容量为2, 指通道中能够存放多少元素
defer : 延迟的资源关闭
并发安全 Lock
对变量做两千次+1操作,5个协程并发执行
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 1:= 0; i< 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
printl("WithLock:",x)
}
执行后:withoutlock:8382, withlock:10000
WaitGroup
通过Wait进行阻塞
依赖管理
Go依赖管理的演进路线
GOPATH -> GO Vendor -> Go Module
不同环境(项目)依赖的版本不同
控制依赖库的版本
GOPATH
一个Go项目的工作区
- bin 项目编译的二进制文件
- pkg 项目编译的中间产物,加速编译
- src 项目源码
GOPATH 弊端
无法实现package的多版本控制
Go Vendor
项目目录下增加vendor文件,所以依赖包副本形式放在 $ProjecrRoot/vendor 依赖寻址方式:vendor => GOPATH 通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题
Go Vendor 弊端
版本可能不兼容
Go Module
通过 go.mod 文件管理依赖包版本
通过 go get/go mod 执行工具管理依赖包
终极目标:定义版本规则和管理项目依赖关系
依赖管理三要素
配置文件,描述依赖,包如何唯一定位 - go.mod
中心仓库管理依赖库 - Proxy
本地工具 - go get/go mod
依赖配置 - go.mod
go 1.16 原生库版本号
require:描述单元依赖,根据path和版本号唯一定位某个版本/提交
依赖配置 - version
版本规则,分为基于语义化版本和基于commit伪版本
语义
不同MAJOR间代码隔离,MINOR新增的函数或功能,PATCH代码bug的修复
V1.3.0来源于git中tag的概念
commit
伪版本+时间戳+哈希码前缀
依赖配置 - indirect
indirect - 用于标识非直接依赖
依赖配置 - incompatible
标识可能存在不兼容的代码逻辑
依赖配置 - 依赖图
B,选择满足本次构建的最低的兼容版本
依赖分发 - 回源
github:代码托管的系统平台
依赖分发 - Proxy
保证依赖稳定性,实现稳定和可靠的依赖分发
依赖分发 - 变量 GOPROXY
工具 - go get
工具 - go mod
go mod init - 初始化,创建go.mod文件
go mod download - 下载模块到本地缓存
go mod tidy - 增加需要的依赖,删除不需要的依赖
测试
事故:
营销配置错误,导致非预期用户享受权益,资金损失
用户提现,幂等失效,短时间可以多次提现,资金损失
代码逻辑错误,广告位被占,无法出广告,收入损失
代码指针使用错误,导致APP不可用,损失
测试是避免事故的最后一道屏障
测试:
回归测试:手动通过终端回归场景
集成测试:对系统功能维度做测试验证
单元测试:面对测试开发阶段,开发者对单独的函数模块做功能验证
从上到下,覆盖率逐层变大,成本却逐层降低
单元测试
保证质量
提升效率
单元测试 - 覆盖率
左边是函数,右边是单测,通过go test 加上 cover参数,就能在运行test的同时计算出代码测试的覆盖率,这里是66.7%,意思为函数的前两行有被验证过,最后一行没有运行到所以没被测试到
修改后的下一张图,这次覆盖率到了100%
单元测试 - Tips
一般覆盖率:50%-60%,较高覆盖率80%+
测试分支相互独立,全面覆盖
测试单元粒度足够小,即要求函数是足够小的,函数单一职责
单元测试 - 依赖
单元测试的两个目标:幂等,稳定
幂等:重复运行一个测试的case时得到结果是一样的
稳定:单元测试是能够相互隔离的,能在任何时间,任何函数独立运行
单元测试 - 文件处理
单元测试 - Mock
monkey:开源测试包,
测试时调的其实是打桩函数
基准测试
优化代码,需要对当前代码分析
内置的测试框架提供了基准测试的能力
基准测试 - 例子
基准测试- 运行
基准测试 - 优化
性能提高
fastrand牺牲了一定的一致性,但提高了性能
项目实战
需求描述
社区话题页面
- 展示话题(标题,文字描述)和回帖列表
- 暂不考虑前端页面实现,仅仅实现一个本地web服务
- 话题和回帖数据用文件存储 分层结构
- 数据层:数据Model,外部数据的增删改查
- 逻辑层:业务Entity,处理核心业务逻辑输出
- 视图层:视图view,处理和外部的交互逻辑