GO语言进阶与依赖管理
这是我参与【第五届青训营】伴学笔记创作活动的第2天
前言
本文记录了本人对Go语言的进阶部分和依赖管理的理解,当然也有自己没有掌握的部分。
一、本节课重点
-
- 语言进阶、并发编程
- Goroutine
- csp
- channel
- 并发安全 Lock
- waitGroup
-
- 依赖管理
- Go依赖管理演进
- 依赖管理三要素
- 依赖配置
-
- 单元测试
- 单元测试
- 单元测试-依赖
- 单元测试-文件处理
- 单元测试-Mock
- 基准测试
二、详细介绍
语言进阶、并发编程
并发:多线程程序在一个核的cpu上运行
并行:多线程程序在多个核的cpu上运行
并行是实现并发的手段
为什么Gp语言快?
Go可以充分发挥多核优势、高效运行
1.1 Goroutine(协程)
- 协程:用户态,轻量级线程,栈KB级别 创建、调度由GO本身完成
- 线程:内核态,线程跑多个协程,栈MB级别 消耗资源,Go能一次创建上万个协程,所以Go语言能高并发
package main
import (
"fmt"
"time"
)
func hello(i int) {
println("Hello goroutine :" + fmt.Sprint(i))
//可以写成fmt.println("Hello goroutine :",i)
}
func HelloGoroutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
//go用来指定睡眠时间的函数为time.Sleep()
//time.Second是一个Duration类型,表示1s的时间间隔,乘系数3就是3s的时间间隔
}
func main() {
HelloGoroutine()
}
1.2 csp
GO提倡通过通信共享内存而不是通过共享内存而实现通信
1.3 channel
make (chan 元素类型,[缓冲大小]) 根据是否有缓冲区大小而分为无缓冲通道和有缓冲通道
- 无缓冲通道 make(chan int)
- 有缓冲通道 make(chan int,2) 2表示缓冲大小为2
箭头代表通道方向,代表Gorountine1发送给Gorountine2
无缓冲通道:发送方与接收方同步化,也被称为同步通道
有缓冲通道:会发生阻塞
func CalSquare() {
src := make(chan int)
dest := make(chan int, 2)
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)
}
}
func main() {
CalSquare()
}
在Go语言中,使用defer关键字延迟一个函数或方法的执行,defer语句会在所有函数最后去执行
一开始使用defer close(),延迟执行close,关闭文件
当函数中出现多个defer语句,则在函数的最后逆序执行defer语句。(遵循栈的操作,先进后出)
func main() {
defer fmt.Println("defer测试完毕")
defer fmt.Println("world")
defer fmt.Print("hello ")
fmt.Println("测试defer")
}
如果defer语句遇到了参数传递。实际上,defer语句调用时参数就已经传递了,只是相关函数和语句会被放到函数的最后执行。后续语句对于参数的修改和先前的defer语句就没有关系了。
func main() {
num := 10
fmt.Println("定义num:", num)
defer fmt.Println("defer参数传递 num:", num)
num *= 2
fmt.Println("计算后的num:", num)
}
1.4 并发安全 Lock
var (
x int64
lock sync.Mutex
//互斥锁,当goroutine获得Mutex后,其他goroutine只能等这个goroutine释放该Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
//加锁
x += 1
lock.Unlock()
//解锁
//可以修改为 lock.LOck() defer lock.Unlock() x+=1
}
}
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)
fmt.Println("WithoutLock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
fmt.Println("WithLock:", x)
}
func main() {
Add()
}
1.5 WaitGroup 实现并发任务同步
计数器: 开启协程 +1;执行结束 -1;主协程阻塞直到计数器为0
package main
import (
"fmt"
"sync"
)
func hello(i int) {
println("Hello goroutine :" + fmt.Sprint(i))
}
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5) //Add(delta int) 计数器 +delta
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done() //Done() 计数器 -1
hello(j)
}(i)
}
wg.Wait() //Wait() 阻塞直到计数器为0
}
func main() {
ManyGoWait()
}
2. 依赖管理
单体函数,依赖原生的SDK
复杂项目,工程项目不可能基于标准库0~1编码搭建,关注业务逻辑,框架、集合等依赖SDk的引入 管理依赖库是很重要的
2.1 Go依赖管理演进
GOPATH -> GOVendor -> GOModule
不同环境(项目)依赖的版本不同
控制依赖库的版本
2.1.1 GOPATH
环境变量 $GOPATH
项目代码直接依赖src下的代码
go get 下载最新版本的包到src目录下
弊端:无法实现package的多版本控制
2.1.2 GoVendor
项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vender
依赖寻址方式: vender-> GOPATH
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题
弊端: 无法控制依赖的版本,更新项目可能出现依赖冲突,导致编译出错
2.1.3 GoMOdule
通过go.mod文件管理依赖包版本
通过go get/go mod指定工具管理依赖包
终极目标: 定义版本规则和管理项目依赖关系
2.2 依赖管理的三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 proxy
- 本地工具 go get/mod
2.3.1 依赖配置-go.mod
- 依赖管理基本单元
- 原生库
- 单元依赖
2.3.2 依赖配置-version
- 语义化版本
- 基于commit伪版本
{MINOR} ${PATCH}
MAJOR 大版本,不同的MAJOR是不兼容的,代码隔离
MINOR 新增函数功能,在MINOR下保持兼容
PATCH 代码bug修复
vx.0.0 代表版本前缀 yyyymmddhhmmss代表时间戳 abcdefgh1234代表12位哈希码前缀
2.3.3 indirect
对于没有直接表示的模块会在go.mod 中加上//indirect
a->b->c a->b 表示直接依赖 a->c 表示间接依赖
incompatible
2.3.4 依赖配置-依赖图
若是有C1.5,也选择1.4版本,因为C1.4版本已经够编译了,不需要其他版本了,1。4版本是最低的兼容版本了
2.3.5 依赖分发-回源
无法保证构建稳定性 增加/修改/删除/软件版本
无法保证依赖可用性 删除软件
增加第三方压力 代码托管平台负载问题
依赖分发-Proxy
2.3.6 依赖分发-变量GOPROXY
2.3.7 工具-go get
2.3.8 工具-go mod
3.单元测试
测试是避免事故的最后一道屏障
3.1单元测试
单元测试能决定代码质量的保证,效率的提升
3.1.1 单元测试-规则
-
所有测试文件以_test.go结尾
-
func TestXxx(*testing.T)
-
初始化逻辑放到TestMain中
3.1.2 单元测试-assert
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectOutput := "Tom"
assert.Equal(t, expectOutput, output)
}
func HelloTom() string {
return "Tom"
}
3.1.3 单元测试-覆盖率
import "testing"
func JudgePassLine(score int16) bool {
if score >= 60 {
return true
} else {
return false
}
}
func TestJudgePassLineTrue(t *testing.T) {
isPass := JudgePassLine(70)
assert.Equal(t, true, isPass)
}
若提高覆盖率,该加上这部分代码,覆盖率达到100%
func TestJudgePassLineFalse(t *testing.T) {
isPass := JudgePassLine(50)
assert.Equal(t, false, isPass)
}
-
一般覆盖率:50%~60%,较高覆盖率:80%+,需要的成本提高
-
测试分支相互独立,全面覆盖
-
测试单元粒度足够小,函数单一职责
3.2 单元测试-依赖
- 幂等:重复运行同一个case,结果与之前一致
- 稳定:指单元测试相互隔离,可以独立运行
3.3 端元测试-文件处理
func ReadFirstLine() string {
open, err := os.Open("log")
defer open.Close()
if err != nil {
return ""
}
scanner := bufio.NewScanner(open)
for scanner.Scan() {
return scanner.Text()
}
return ""
}
func ProcessFirstLine() string {
line := ReadFirstLine()
destLine := strings.ReplaceAll(line, "11", "00")
return destLine
}
func TestProcessFirstLine(t *testing.T) {
firstLine := ProcessFirstLine()
assert.Equal(t, "line00", firstLine)
}
3.4 单元测试-Mock
快速Mock函数
- 为一个函数打桩
- 为一个方法打桩
func Patch(target, replacement interface{}) *PatchGuard {
t := reflect.ValueOf(target)
r := reflect.ValueOf(replacement)
patchValue(t, r)
return &PatchGuard{t, r}
}
func Unpatch(target interface{}) bool {
return unpatchValue(reflect.ValueOf(target))
}
func TestProcessFirstLineWithMock(t *testing.T) {
monkey.Patch(ReadFirstLine, func() string {
//对ReadFirstLine打桩测试,不在依赖本地文件
return "line110"
})
defer monkey.Unpatch(ReadFirstLine)
line := ProcessFirstLine()
assert.Equal(t, "line000", line)
}
3.5 基准测试
- 优化代码,需要对当前代码分析
- 内置的测试框架提供了基准测试的能力
//随机选择执行服务器
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)]
}
测试运行
func BenchmarkSelect(b *testing.B) {
InitServerIndex()
b.ResetTimer()
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()
}
})
}
优化,提高性能近百倍
func FastSelect() int{
return ServerIndex[fastrand.Intn(10)]
}
总结
本次课程本人学到了很多知识,知道了Go语言的并发编程,依赖管理,单元测试,进一步掌握Go语言的知识, 争取下次课程能再进一步。期待下节课的到来