Go进阶依赖
并发与并行
Go可以充分发挥多核优势,高效运行。根据系统的状态分配CPU运行。可以让多线程程序在一个核的CPU上运行达到并发效果,也可以令多线程程序在多个核的CPU运行达到并行效果。
Goroutine
协程:用户态,轻量级线程,栈KB级别
线程:内核态,线程可以并发跑多个协程,栈MB级别
可以说正是因为goroutine的切换代价远远小于线程,不用经过内核态,这是让Go拥有远超别的编程语言的高并发性的重要基石。
CSP
Go的协程不同于Java,C++等采用通过共享内存实现通信,Go的推荐的并发模型为通过通信共享内存,不过也保留了通过共享内存实现通信的方式。
通道为构建一个channel.
make(chan 元素类型,[缓从大小])
make(chan int)//无缓冲通道,会让协程同步
make(chan int,2)//有缓冲通道
Lock
Go的协程实现了并发,但这样的并发顺序不能控制,对于要处理共享内存的并发,需要有并发安全控制,采用sync.Mutex进行加锁处理,实质采用的是sleep这样的一个方法。
var lock sync.Mutex
lock.Lock()
//to do
lock.Unlock()
WaitGroup
计数器,开启协程+1;执行结束协程-1;主协程阻塞直到计数器为0.
//三个公有的方法
Add(delta int)//计数器+delta
Done()//计数器-1
Wait()//阻塞直到计数器为0
开始协程时调用Add,结束时调用Done
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)//(i)直接调用匿名函数执行,传入参数为i
}
wg.Wait()
}
依赖管理
GOPATH->GO Vender->Go Module
目前采用最广的是Go Module管理,也是Go语言依赖管理方法迭代至今最新的技术。
- 通过Go.mod 文件管理依赖包版本
- 通过go get/go mod指令工具管理依赖包
这样做项目依赖管理的终极目标:定义版本规则和管理项目依赖关系
三要素
- 配置文件,描述依赖关系定位 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
依赖配置
主要依赖代码的版本。对于同一模块的不同版本加载时,取兼容版本中最低兼容版本加载
依赖分发
即依赖从哪里下载,如何下载的问题。不直接从平台获取源码,因为不能保证依赖的可用性(没准作者把代码删了哈哈)。
Proxy:服务站点,缓存源站中的内容,版本也不会改动。稳定且可靠
GOPROXY="proxy1.cn,https://proxy2.cn,…"
对已有的缓存进行查找,均查找不到后,回源进入direct即第三方源站查找
依赖工具
go get example.org/pkg + 参数
参数:
@update 默认
@none 删除依赖
@v1.1.2 tag版本,语义版本
@23dfdd5 特定的commit
@master 分支的最新commit
个人感觉他跟git的项目管理基本一致。
go mod + 指令
指令:
init 初始化,创建go.mod文件
download 下载模块到本地缓存
tidy 增加需要的依赖,删除不需要的依赖
测试
- 回归测试:模拟用户使用产品,进行测试
- 集成测试:系统功能的自动化测试等
- 单元测试:对单独的函数或者模块进行测试
单元测试
规则
-
所有测试文件以
_test.go结尾。开发时就不要这么命名啦!! -
测试函数命名:
func TestXxx(t *testing.T) -
初始化逻辑放到TestMain中
实际代码
//开发函数
func HelloTom() string{
return "Jerry"
}
//测试函数
func TestHelloTom(t *testing.T){
output := HelloTom()
expectOutput := "Tom"
if output != expectOutput {
t.Errorf("Expected %s do not match actual %s",expectOutput,output)
}
}
也可以用assert进行测试,用法如下:
import(
"github.com/stretchr/testify/assert"
"testing"
)
func TestHelloTom(t *testing.T){
output := HelloTom()
expectOutput := "Tom"
assert.Equal(t,expectOutput,output)
}
依赖
测试开发追求幂等与稳定,即要重复运行结果一致和单元测试独立运行。验证代码的正确性和独立性。
依赖有文件,数据库,缓存等,,此时可以采用Mock进行测试
常用的Mock组件包:https://github.com/bouk/monkey
Mock即是用一个函数(方法)替换另一个函数(方法)。打桩
实现:在运行测试函数时,将原函数地址替换成打桩函数地址。
func TestProcessFirstLineWithMock(t *testing.T){
monkey.Patch(ReadFirstLine,func() string{
return "line110"
})//替换ReadFirstLine函数为打桩函数
defer monkey.Unpatch(ReadFirstLine)//删除打桩函数
line := ProcessFirstLine()
assert.Equal(t,"line000",line)
}
上述Mock方法即实现了原本对有文件读入依赖的ReadFirstLine函数替换,去除对文件依赖,直接测试ProcessFirstLine函数
基准测试
用于代码性能测试,瓶颈分析。
例子是对一个含rand随机的函数做测试
//基准测试函数名以Benchmark开头
func BenchmarkSelect(b *testing.B){
b.ResetTimer()//重置时间
for i:=0;i<b.N;i++{
Select()
}
}
func BenchmarkSelectParallel(b *testing.B){
b.ResetTimer()
b.RunParallel(func(pb *testing.PB){//并行测试
for pb.Next(){
Select()
}
})
}
b.N代表用例中测试代码的循环次数,首先从1开始,能在1s内完成,则go test会逐渐增加b.N再次运行
go test命令的一些参数:
-run regex:运行regex所匹配的所有单元测试-bench regex:运行regex所匹配的所有基准测试-cpu:设置执行测试的CPU数量,以逗号分割-benchmem:输出内存分配情况-count:设置执行轮次-benchtime:设置执行时间,默认1s
学习感想
对Go的依赖管理和测试的学习很明显的丰富了我学习Go语言的眼界,还帮我解释了在Go学习中的bug调试方法,以后对测试开发岗也不是一窍不通了,大致知道了测试的过程,以及其追求的目标是什么。同样的方法对开发时的问题也会有很大的帮助。