这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记
GO-学习笔记
Go并发编程
- 在用户态实现资源调度
并发与并行
- 并发,同一个CPU,切换执行
- 并行,同时多个CPU上,一起执行,是实现并发的手段
运行机制
- 协程,用户台,轻量级线程,栈MB级别,用户级线程
go中的并发运行单位
- 线程,内核台,线程跑多个协程,栈KB级别
开启协程
- 关键字
go
go func(word string) {
//执行完成 执行Done()
defer wg.Done()
queryCaiyunai(word)
}(word) //该word为参数
协程间通信
通信共享内存
- go提倡通过通信共享内存,通过通道
chan
共享数据 - 如果指定大小,为有缓冲通道;没有指定大小,为无缓冲通道,也是同步通道
如果达到缓冲大小,则会阻塞
ch := make(chan int, 10)
arr := []int{1, 2, 3, 4, 5}
for _, n := range arr {
// 入通道
ch <- n
}
// 出通道
a, b, c, d, e := <-ch, <-ch, <-ch, <-ch, <-ch
fmt.Println(a, b, c, d, e)
并发编程中的应用
- 调用
go sum(arr[:len(arr)/2], ch)
go sum(arr[len(arr)/2:], ch)
x, y := <-ch, <-ch
fmt.Println(x, y, x+y)
sum
函数
func sum(nums []int, ch chan int) {
sum := 0
for _, n := range nums {
sum += n
}
ch <- sum
}
共享内存来通信
sync.Mutex
锁
var (
x int64
lock sync.Mutex
)
func main() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
WaitGroup
- 维护了计时器
- 创建
wg := &sync.WaitGroup{}
- 计数+1
wg.Add(2)
- 计数-1
wg.Done()
底层调用
wg.Add(-1)
- 计数不为0时阻塞
wg.Wait()
标准用法
wg := &sync.WaitGroup{}
wg.Add(2)
word := os.Args[1]
// 匿名函数
go func(word string) {
//执行完成 执行Done()
defer wg.Done()
queryCaiyunai(word)
}(word) //该word为参数
go func(word string) {
defer wg.Done()
queryHuoshan(word)
}(word)
wg.Wait()
Errgroup
- Errgroup 同步扩展库
- 使用
eg := errgroup.Group{}
eg.Go(func() error {
err := queryCaiyunai(word)
return err
})
eg.Go(func() error {
err := queryHuoshan(word)
return err
})
if err := eg.Wait(); err != nil {
fmt.Println(err)
}
依赖管理
- 不同环境/项目的依赖版本不同,且控制依赖库的版本
- 同一个项目中的两个子项目依赖不同的版本时,选择最新的兼容版本
GOPATH
- 打开
cd $GOPATH
bin
项目编译的二进制文件pkg
项目编译的中间产物src
项目源码go get
下载最新版本的包到src
下- 弊端,无法实现
package
的多版本控制
Vendor
- 在项目目录下增加
vendor
目录,所有依赖包以副本形式存放在vendor
下 vendor
在GOPATH
下寻找- 弊端,同一个项目,依赖的包版本冲突,无法控制依赖的版本
Go Module
- 通过
go.mod
管理依赖包 - 通过
go get/go mod
指令工具管理依赖包 go get
拉去某个包,类比cocoapods
中的pod install
- 在
go mod
之后,进行依赖go install
安装bin
文件,不会修改go.mod
go mod
init
初始化download
下载模块tidy
增加需要的依赖,删除不需要的依赖
依赖管理三要素
- 类比
maven
go.mod
配置文件,描述依赖Proxy
中心仓库管理依赖库go get/mod
本地工具
依赖分发
Proxy
,保证依赖的稳定性- 配置
GOPROXY
,为url
列表
依次查找
"url1, url2, direct"
,direct
为源站
测试
- 测试文件
_test.go
结尾 - 函数格式
func Testxxxx(t *testing.T) {
//test...
}
- 导入包
import "testing"
TestMain
进行初始化逻辑,执行test
方法时执行
func TestMain(m *testing.M) {
//...资源管理/初始化操作
m.Run()
}
go test
命令行工具,执行当前包下的所有test
方法
测试的代码覆盖率,
go test go_test.go go.go --cover
assert
- 验证是否满足条件
s := queryName()
m := "output1"
assert.Equal(t, m, s)
mock机制
- 保证测试的幂等和稳定
幂等,多次相同的操作结果一致
- 替换函数,将真正要执行的函数,替换成模拟函数
真实情况下,函数的功能执行往往需要复杂的初始化过程,可以通过
mock
打桩,进行替换,用不同的函数返回值进行测试
- 框架
bou.ke/monkey
- 使用,将
ReadFirstLine
函数的执行,替换成后面闭包的执行,只需要模拟真实的返回值即可
运行时,替换成对应的子函数调用
func TestProcessFirstLineWithMock(t *testing.T) {
monkey.Patch(ReadFirstLine, func() string {
return "line110"
})
// 删除补丁
defer monkey.Unpatch(ReadFirstLine)
line := ProcessFirstLine()
assert.Equal(t, "line000", line)
}
基准测试
- 测试单元性能和CPU损耗,多次以不同的数量级进行运行
Benchmark
开头
func BenchmarkSelect(b *testing.B) {
InitServerIndex()
// 重设其实时间
b.ResetTimer()
// b.N 数量级,会不断变大
for i := 0; i < b.N; i++ {
Select()
}
}
基准并发测试
func BenchmarkSelectParallel(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Select()
}
})
}