Go语言进阶与依赖管理|青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
1. 并发编程简述
并发是指多线程在一个核的cpu上运行,通过时间片的切换实现同时运行
并行是利用多核实现多线程运行
1.1Goroutine
线程:内核态,线程跑多个协程,栈KB级别
协程:用户态,轻量级线程,栈MB级别
在调用函数前面加一个go 关键字表示为一个函数创建一个协程来运行
func hello(i int) {
println("hello goroutine" + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
func main() {
HelloGoRoutine()
}
1.2 CSP
提倡通过通信来共享内存而不是通过共享内存实现通信
通过共享内存实现通信利用互斥量对内存进行加锁,一定程度上会影响程序性能
1.3 Channel
引用类型,通过make(chan 元素类型,[缓冲大小])创建
根据缓冲大小分为有无缓冲通道
make(chan int)
make(chan int,2)
无缓冲通道
发送Gorountine和接收Gorountine同步化,也被称为同步通道
有缓冲通道
缓冲大小也就是通道容量代表通道中能存多少元素,类比生产消费,货架满了会阻塞发送,直到有人取走才能继续放,有缓冲通道能解决生产和消费速度不均衡带来的执行效率问题
src := make(chan int)
dest := make(chan int, 3)
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)
}
1.4 并发安全Lock
避免对共享内存进行非并发读写操作可能出错
1.5 WaitGroup
-
Add(delta),计数器+delta;
-
Done(),用来将WaitGroup的计数值减1,其实就是调用了Add(-1);
-
Wait(),调用这个方法的goroutine会一直阻塞,直到WaitGroup的计数值变为0
利用WaitGroup优化1.1代码
func HelloGoRoutine() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
2. 依赖管理
2.1 依赖管理演进
GOPATH->Go Vendor->GoModule
2.1.1 GOPATH
环境变量go项目的工作区
bin 项目编译的二进制文件
pkg 项目编译的中间产物加速编译
src 项目源码
项目代码直接依赖src下的代码
go get下载最新版本的包到src目录下
弊端:A和B依赖于某一package的不同版本,无法实现package多版本控制
2.1.2 Go Vendor
项目目录下增加vendor文件,所有依赖包副本形式放在vendor文件下,依赖寻址方式为vendor->GOPATH,通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题
弊端:无法控制依赖版本,可能出现依赖冲突
2.1.3Go Module
通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包
目的:定义版本规则和管理项目依赖关系
2.2 依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
2.3 依赖配置
2.3.1 go.mod
module 依赖管理基本单元
go xx.xx go原生库版本号
require() 单元依赖,依赖标识[Module Path] [Version/Pseudo-version]
2.3.2 version
- 语义化版本 v1.3.0 v2.3.0 major.minor.patch,不同MJOR间版本隔离,MINOR间版本兼容,PATCH bug修复
- 基于commit 伪版本 版本前缀(语义化版本)-时间戳-12位哈希吗前缀
2.3.3 indirect间接依赖
A->B->C A->B直接依赖 A->C间接依赖
2.3.4 incompatible
对于 MAJOR 主版本在 V2 及以上的模块,go.mod 会在模块路径增加 /vN 后缀 。这能让 Go Module 按照不同的模块来处理同一个项目不同 MAJOR 主版本的依赖。
- 由于 Go Module 是在 Go 1.11 才实验性地引入,所以在这个更新提出之前,已经有一些仓库打上了 V2 或者更高版本的 tag 了。
- 为了兼容这部分仓库,对于没有 go.mod 文件并且 MAJOR 主版本在 V2 及以上的依赖,会在版本号后加上
+incompatible后缀。表示可能会存在不兼容的源代码。
依赖关系:选择满足本次构建最低的兼容版本
2.3.5依赖分发-回源
表示依赖去哪里下载如何下载的问题
- Go 的依赖大部分托管在 GitHub 上。Go Module 系统中定义的依赖,最终都可以对应到 GitHub 中某一项目的特定提交或版本。
- 对于 go.mod 中定义的依赖,则直接可以从对应仓库中下载指定依赖,从而完成依赖分发。
弊端:
- 无法保证构建稳定性 (增加/删除/修改软件版本)
- 无法保证依赖可用性
- 增加第三方压力(代码托管平台负载问题)
解决方案-Proxy
Go Proxy是一个服务站点,他会缓存源站中的软件/代码内容,且版本不会改变,在作者删除后依然可用,实现了稳定和可靠的依赖分发
使用Go Proxy后,构建时会直接从Go Proxy站点拉取依赖
2.3.6 依赖分发-变量 GOPROXY
Go Module 通过 GOPROXY 环境变量控制如何使用 Go Proxy
GOPROXY 是一个 Go Proxy 站点 URL 列表
GOPROXY = "https://proxy1.cn, https://proxy2.cn, direct"
依赖寻址路径为:先从 proxy1 下载依赖,如果 proxy1 不存在,再从 proxy2 寻找,如果 proxy2 仍不存在,则会回源到源站直接下载依赖,并缓存到 Go Proxy 站点中
2.3.7 工具-go get
go get example.org/pkg
默认go get会拉取major版本最新提交
参数:
- @update 默认
- @none 删除依赖
- @v1.1.2 tag版本,语义版本
- @23dfdd5 特定的commit
- @master 分支的最新commit
2.3.8 工具-go mod
go mod
参数:
- init 初始化,创建go.mod文件(项目开始前必需步骤)
- download 下载模块到本地缓存
- tidy 增加需要的依赖,删除不需要的依赖