Go语言第二课笔记(并发、依赖管理以及测试)| 青训营笔记

95 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记

主要内容为第二节课上课知识点记录,不包含项目实践

并发与并行

1.1 Goroutine

并发:多线程在一个核的cpu上运行 利用时间片切换
并行:多线程程序在多个核的cpu上运行

协程Goroutine 用户态、轻量级线程、栈KB级别(上课时老师这里好像有口误)
线程:内核态、线程跑多个协程,栈MB级别

例1:快速 打印hello

func hello(i int) {
	fmt.Println("Hello Goroutine: " + fmt.Sprintln(i))
}

func HelloGoRoutine(){
    for i:=0;i<5;i++{
        go func(j int){
            hello(j)
        }(i) 
    }
    time.Sleep(time.Second)
}

1.2 CSP(Communicating Sequential Processes)

Go提倡通过 通信共享内存 而不是通过共享内存来通信
通过通信共享内存 主要是利用通道 chan
通过共享内存来通信 利用锁获得临界区权限 会带来数据竞态 影响程序性能

1.3 Channel

make(chan 元素类型,[缓冲大小])
  • 无缓冲通道 make(chan int) //也被称为同步通道
  • 有缓冲通道 make(chan int,2)
// A子协程发送数字0~9
// B子线程计算输入数字的平方
//主协程输出最后的平方数
func CalSquare()  {
src := make(chan int)
dest := make(chan int,3)
//A
go func() {
defer close(src)
for i := 0;i<10;i++ {
src <- i
}
}()

//B
go func() {
defer close(dest)
for i := range src{
dest <- i*i
}
}()

for i := range dest{
fmt.Println(i)
}

}

1.4 并发安全 Lock

//对变量执行2000次+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)
	fmt.Println("without lock", x)

	x = 0
	for i := 0; i < 5; i++ {
		go addWithLock()
	}
	time.Sleep(time.Second)
	fmt.Println("without lock", x)
	
}

img.png

1.5 WaitGroup

使用WaitGroup实现协程同步 三个方法 Add() Done() Wait()

快速打印的修改

func hello(i int) {
fmt.Println("Hello Goroutine: " + fmt.Sprintln(i))
}

func HelloGoRoutine() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
//time.Sleep(time.Second)
}

小结 Goroutine:通过协程实现高并发 Channel:提倡通过通信来实现共享内存 Sync包:实现并发安全和同步

依赖管理

依赖指各种开发包和第三方开发组件和工具,项目开发不可能基于标准库0~1编码搭建 ###2.1 Go依赖管理演进 GoPath -> Go Vendor -> Go Module

GOPATH

环境变量GOPATH,GOPAHT目录下有bin pkg src三个文件夹。

  • 项目代码直接依赖src下的代码
  • go get下载最新版本的包到src目录下 弊端:A,B项目依赖于某一package的不同版本时,无法同时运行 GOPATH无法实现package的多版本控制

GOVendor

  • 在项目目录下增加vendor文件夹,所有依赖包副本形式放在 $ProjectRoot/vendor
  • 依赖寻址方式 vendor -> gopath 通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题 弊端:项目A的两个依赖包B,C都同时依赖与包D的不同版本时,可能出现错误

Go Module

  • 通过go.mod文件管理依赖包版本
  • 通过go get/go mod 指令工具管理依赖包

###2.2 依赖管理三要素 1.配置文件,描述依赖 go.mod 2.中心仓库管理依赖库 Proxy 3.本地工具 go get/mod

2.3.1 依赖配置之 go.mod讲解

module example/project.app  依赖管理基本单元

go 1.16 原生库

require(    单元依赖
   example/lib1 v1.0.2
   example/lib2 v1.0.0  //indirect
   example/lib3 v0.0.0-20220401081311-c38fb59326b7
   example/lib4 v3.2.0+incompatible
)

2.3.2 依赖配置之 version

版本规则有 语义化版本和基于commit的伪版本

  • 语义化版本

    MAJOR.{MAJOR}.{MINOR}.${PATCH} 相同的MAJOR不同的MINOR是兼容的 eg v1.3.0

  • 基于commit的伪版本

    v x.0.0-yyyymmddhhmmss-abcdefgh123 eg:v0.0.0-20220401081311-c38fb59326b7

2.3.3 依赖配置之 indirect

indirect表示非直接依赖

若有 A->B->C 则称A->B直接依赖 A->C 间接依赖

2.3.3 依赖配置之 incompatible

对主版本2+的依赖且没有go.mod的文件 会用+incompatible标识

依赖图

img_1.png 答案为B 选择满足构建的最低的兼容版本

2.3.5依赖分发 回源

用第三方软件平台如github

*无法保证构建的稳定性

因为作者可能增加/修改/删除软件版本

*无法保证依赖的可用性

被删去

*增加第三方压力

代码托管平台负载问题

因此产生PROXY,Go通过环境变量GOPORXY来控制服务站点

2.3.7 工具 go get

go get example.org/pkg @update 默认 @none 删除 @v1.1.2 tag版本 @23dfdd4 特定的commit @master 分支最新的commit

2.3.8 工具 go mod

go mod init 初始化,创建go.mod文件
go mod tidy 增加需要的依赖 删除不需要的依赖
go mod download 下载模块到本地缓存

测试

分为回归测试(上线以后)、集成测试(开发过程末)、单元测试(开发过程中),从前到后,覆盖率逐渐变大,成本逐层降低

3.1单元测试

单元测试的规则

1.所有的测试文件以_test.go结尾 

2.func TestXxx(* testing.T)

3.初始化逻辑放到TestMain(是一个函数)中

单元测试运行命令

go test [flags][packages]

img_2.png

覆盖率

用覆盖率衡量代码是否经过足够的测试 go test XXX_test.go XXX.go --cover

mock测试

monkey : github.com/bouk/monkey

目的是摆脱对本地数据的强依赖

基准测试

主要是对性能上的测试