这是我参与「第三届青训营 -后端场」笔记创作活动的的第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)
}
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的伪版本
-
语义化版本
{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标识
依赖图
答案为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]
覆盖率
用覆盖率衡量代码是否经过足够的测试
go test XXX_test.go XXX.go --cover
mock测试
monkey : github.com/bouk/monkey
目的是摆脱对本地数据的强依赖
基准测试
主要是对性能上的测试