Go语言进阶与依赖管理
语言进阶-并发编程
-
并发:
- 多线程运行运行在cpu的一个核
- 多线程运行运行在cpu的多个核
-
Goroutine
- 协程,协程运行在cpu用户态,可以看做轻量级的线程,栈mb级别
- 线程,运行在cpu内核态,一个线程可以运行多个协程,栈kb级别
-
go关键字启动一个 goroutine,
-
var i=1 go func(j int){ fmt.Println(j) }(i) time.sleep(time.second) //确保主线程在协程结束后退出 -
携程之间通信DSP communiting sequential processes
-
-
通过通信共享内存而不是共享内存实现goroutine间通信
-
Channel通道
-
make (chan int) 创建无缓冲通道
-
make (chan int,2) 创造有缓冲通道
-
int是通信数据类型,2是指通道容量大小
- 简单的例子: 生产-消费模型, 协程输出0-9数字平方, dest代缓冲
-
func ValSquare(){ 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) } }
-
-
-
并发安全 lock sync.mutex
- lock和unlock
-
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 addWithLock() } time.Sleep(time.Second) println("WithLock:"x) x=0 for i:=0; i<5; i++{ go addWithoutLock() } time.Sleep(time.Second) println("WithoutLock:"x) }
-
WaitGroup保证并发任务的同步
-
内部维护了一个计数器, 用来记录协程的数目
-
- Add(), 计数器增加数值
- Done(), 计数器数值减一
- Wait(), 堵塞协程, 只有当技术器数值为0, 函数才能返回
-
func MantGoWait(){ var wg sync.WaitGrou wg.Add(5) //记录5个协程 for i :=0; i < 5; i++{ go func(j int){ defer wg.Done() println(j) }(i) } wg.wait() }
-
依赖管理
-
依赖管理的演进路线
-
GOPATH
-
环境变量, 指向Go的工作目录
-
目录结构
-
项目代码直接依赖于src目录下的代码
-
存在问题: 无法实现package多版本控制
-
-
Go okgWendor
- 在GOPATH基础上, 在每个项目目录下, 增加了vendor目录, 先在项目目录寻找依赖,再到公共依赖库寻找依赖
- 存在问题: 无法展示依赖的层级结构
-
GO Module
-
依赖管理三要素
- 配置文件 go.mod
- 中心仓库 Proxy
- 本地工具 go get/mod
-
Go.mod文件
-
版本配置-version
- 语义版本
${MAJOR}.${MINOR}.${PATCH} - 抑郁commit 伪版本
- 语义版本
-
关键字
-
indirect 标识非直接依赖
-
Incompatible 标识没有go.mod,并且版本2+的依赖
-
, 会在版本号号=后加这个关键字, 表示可能会有不兼容问题
-
依赖版本选择时, 会选择最低的兼容版本
-
依赖分发
- 直接从远程库下载的问题
- Proxy
- 寻找依赖顺序: proxy1->proxy2->Direct
-
工具
- go get
- go mod
-
-
-
测试
-
一些真实发生的事故
-
测试分类
- 回归测试: 实际服务体验
- 集成测试: 对对外提供的接口矩进行自动化测试
- 单元测试: 对函数,模块进行测试
-
测试规则
- 测试文件命名: _test.go结尾
- 测试函数名: func TestXxx(*testing.T)
- 初始化逻辑放到TestMain
-
func TestMain(m *testing.T){ //测试前, 初始化, 数据准备 code :=m.Run() //测试后, 释放资源等 os.exit(0) }
-
简单例子
-
func HelloTom() string{ return "Jerry"; } -
func TestHelloTom(t *testing.T){ output :=HelloTom() expectOutput := "Tom" if output !=exceptOutput{ t.Errorf("Excepted %S do not match actual %S ",exceptOutput, output) } } - go test hello_test.go hello.go
-
-
单元测试-assert
-
导入开源的assert包
github.com/stretchr/testify/assert -
assert.Equal(t,expectOutput, output) -
评估测试-覆盖率
-
执行程序, 显示覆盖率
-
go test hello_test.go hello.go --cover -
cover计算: 运行到的代码行数/总代码行数
-
一般覆盖率:50-60, 较高为80+
-
测试要求
- 测试分支相互独立, 覆盖全面
- 单元测试粒度足够小, 函数职责单一
-
-
-
单元测试-依赖
-
要求
- 幂等性: 运行多次,结果是一致的
- 稳定性: 任何时间,任何函数中运行依赖, 不依赖网络或者本地文件等环境,这就是mock测试
-
-
单元测试-Mock
- monkey开源mock测试库: github.com/bouk/monkey
- 为函数打桩
- 为方法打桩
-
单元测试文件管理
-
基准测试: 测试程序的运行性能和cpu的消耗程度
- 负债均衡例子
-
package main import ( "math/rand" "testing" ) var ServerIndex [10]int func InitServerIndex() { for i := 0; i < 10; i++ { ServerIndex[i] = i + 100 } } func Select() int { return ServerIndex[rand.Intn(10)] } //基准测试函数Benchmaek开头, 入参是b * testing.B //当函数执行时间不超过1秒时, 这里N的值会自动递增,1, 2, 5, 10, 20, 50... func BenchmarkSelect(b *testing.B) { //ResetTimer重置计时器, 避免初始时间的干扰 b.ResetTimer() for i := 0; i < b.N; i++ { Select() } } //协程并发测试, 发现在多协程下程序有效率问题, 因为rand内使用了全局锁 func BenchmarkSelectPapallel(b *testing.B) { InitServerIndex() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { Select() } }) } //可以使用字节开源的randAPI, 可以解决这个问题 //func Select() int { // return ServerIndex[fastrand.Intn(10)] //} -