这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
本篇笔记主要包含以下内容:
- 从并发编程的视角了解Go高性能的本质;
- Go语言依赖管理;
- 单元测试;
- 项目实战。
一、 从并发编程的视角了解Go高性能的本质
1.协程(Goroutine)
(1)特点:用户态、轻量级线程,栈MB级别,用于执行并发任务,线程跑多个协程; (2)使用方法:调用函数时在函数前加go关键字。
go 函数名(参数列表)
(3)扩展:Sleep阻塞,保证在子协程执行完之前主协程不退出。
time.Sleep(time.Second)
2.通信顺序进程(CSP)
(1)go提倡通过通信共享内存而不是通过共享内存而实现通信。 (2)go拥有通信共享内存和通过共享内存而实现通信两种方式。 (3)由协程Goroutine与通道Channel实现。
3.通道(Channel)【通过通信共享内存】
(1)定义:Channel是一种引用类型, 在 gouroutine 间架起了一条管道,在管道里传输数据,实现 gouroutine 间的同步、通信;由于它是线程安全的,所以用起来非常方便;channel 还提供 “先进先出” 的特性;它还能影响 goroutine 的阻塞和唤醒。
(2)使用:make(chan 元素类型,【缓冲大小】)
(3)分类:
- 无缓冲通道:
make(chan int) - 有缓冲通道:
make(chan int,2)
使用无缓冲通道会让通信的两个gorountine同步化,所以无缓冲通道也称为同步通道;使用有缓冲通道则可以解决同步问题。
(4)扩展:资源延迟关闭。
defer close(src)
4.并发安全Lock【通过共享内存而实现通信】
(1)使用lock保护临界区:输出结果符合预期值。
lock.lock() //执行代码 lock.Unlock()
(2)不使用lock保护临界区:输出结果低于预期值,存在并发安全的问题。
5.WaitGroup(计数器)
(1)Add(delta int):开启协程+1;
(2)Done():执行结束-1;
(3)Wait():主协程阻塞直到计数器为0。
(4)优化:
变为
①需要五个协程;②在每个协程结束后,通过Done()方法对协程减一;③通过Wait()进行阻塞。
二、 Go语言依赖管理
1.依赖管理演进:
演进过程--其中Go Module使用广泛。
目的:
- 不同环境(项目)依赖的版本不同
- 控制依赖库的版本
(1).GOPATH
- $GOPATH环境变量,是go项目的工作区。
bin-项目编译的二进制文件; pkg-项目编译的中间产物,加速编译; src-项目源码。
- 项目代码直接依赖src下的代码
- go get下载最新版本的包到src目录下
- 弊端:无法实现package的多版本控制。
(2)Go Vendor
- 方法:
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
依赖寻址方式: vendor => GOPATH
- 弊端:
依赖项目源码,不能很清晰的标识依赖版本的概念。
无法控制依赖的版本。
更新项目又可能出现依赖冲突,导致编译出错。
(3)Go Module
- 通过go.mod文件管理依赖包版本;
- 通过go get/go mod指令工具管理依赖包;
- 终极目标:定义版本规则和管理项目依赖关系。
2.依赖管理三要素
(1)配置文件,描述依赖go.mod
(2)中心仓库管理依赖库Proxy
(3)本地工具go get/mod
3.依赖配置 (1)go.mod
(2)version
- 语义化版本
${MAJOR}.${MINOR}.${PATCH}
不同MAJOR可以不兼容;MINOR做到前后兼容;PATCH做一些代码和BUG的修复。
V1.3.0 V2.3.0
- 基于commit伪版本
vX.0.0-yyyymmddhhmmss-abcdefgh1234
版本前缀-时间戳-12位哈希校验码
v0.0.0-20220401081311-c38fb59326b7 v1.0.0-20201130134442-10cb98267c6c
(3)indirect 将没有直接导入的依赖模块标识为非直接依赖,用indirect标识出来。
(4)incompatible
- 主版本2+模块会在模块路径增加/vN后缀。
- 对于没有go.mod 文件并且主版本2+的依赖会+incompatible
(5)依赖图
- 选择最低的兼容版本