Go 并发编程
并发与并行
并发是交替执行达到宏观上同时执行的效果,而并行是真正意义上的同时执行
Goroutine
这里线程是内核态,相关操作比较重,开销大,栈 MB 级别
协程是用户态,轻量级线程,栈 KB 级别
Go 语言可以说是一个 go 字走天下,只要加上这个关键字就相当于另起了一个 goroutine,操作简单且效率高
CSP
CSP 是 Communicating Sequential Process 的简称,即通信顺序进程
go 语言使用了其理论中的 Process/Channel 对应 goroutine 和 channel。process 可以去尝试接收任意个 channel,channel 对接收它的 process 也不会有相应感知。process 围绕 Channel 进行读写,形成一套 有序阻塞和可预测的并发模型。
操作系统中对于不同进程/线程/协程的通信方式做了很多方案,其中有使用共享内存而达到通信目的,与之不同,go 中 提倡 使用通信(channel)来共享内存而不是共享内存来实现通信
channel
-
无缓冲
信道只有发送方会阻塞,直到有接收方开始接收
-
有缓冲
信道缓冲满了就会阻塞,直到有接收方接收腾出空间
Lock
在 sync 包中的 Mutex 能提供加锁与释放的功能,此外包中还有一些并发类型,比如并发安全的 map。但实际上,仅仅只是单增的操作更适合使用 atomic 中的原子类型
WaitGroup
内部有计数器,可以自行调用方法增减,当计数器不为零时 Wait() 方法会一直阻塞
Add(delta int) :计数器+delta
Done() :计数器-1
依赖管理
Go 语言的依赖管理进化史大致如下:
- GOPATH
- Go Vendor
- Go Module
现在基本使用 Go Module,前两者不如他好用,也可以说他继承了之前包管理手段的优点,摈弃了其他备受诟病的缺陷
GOPATH
早期Golang是使用$GOPATH作为依赖管理的机制,它就相当于一个工作区,依赖下载会存入相应文件夹:
- bin:一般用于存放可执行文件
- pkg:存放静态连接库文件
- src:存放第三方依赖的源码和自己开发的代码
GOPATH 的致命缺陷是它没有对于版本的清晰认知,这是很可怕的。如果编写过 Java,就知道哪怕在 Maven 出现之后对于版本之间的兼容性调试是有多么地狱。而这一切在 GOPATH 中你可能会再次经历一遍……
Go Vendor
由于 GOPATH 初期存在巨大的缺陷,因此 1.5 版本引入了该机制用以优化(主要是多了对包版本的管理)
实际上只是在项目 根目录 下建立了一个 vendor 目录,用于存储当前项目的依赖,因此包引用查询的步骤其实是这样:
- 在当前项目根目录中的
vendor目录中查找 $GOPATH/src下查找
当然这个方案的缺陷显而易见了,vendor 文件夹中存放的包和其他项目不共用,极其容易造成存储空间的浪费,并且项目必须在 $GOPATH/src 下,以及包引用管理很多时候仍旧需要手动操作
虽然
vendor提供了一些命令管理依赖,但是大部分时间还是需要手动的去管理项目依赖的依赖,包括依赖的版本记录,获取和存放等
Go Module
相对来说比较好用的包管理方式,在 Go 1.11 版本引入,操作丝滑流畅并且需要手动操作的部分几乎可以忽略不计。Go Module 是基于 package 的,package 就是由一个文件或者多个文件实现的单一功能,在一个项目当中,会包含一个或多个 package
//module path,格式一般为:仓库名称+module name,如果是本地开发的,一般只有 module name,如果需要发布到远程仓库,比如:github,则需要添加为:github.com/xxx/moduleName
module project
go 1.17
require (
//indirect 注释标识是否是被项目直接依赖的库
//incompatible:库版本大于 2,照理说 module path 后面会变成加上 “/v2” 的形式,但是这里不符合规范,所以加上这个关键字说明后缀不规范
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/aws/aws-sdk-go v1.32.5
//为了区分版本生成的伪版本号,后两段是时间和 commit id
github.com/beego/i18n v0.0.0-20140604031826-e87155e8f0c0
github.com/bugsnag/bugsnag-go v1.5.2 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/client/v2 v2.305.0-rc.1
go.etcd.io/etcd/client/v3 v3.5.0-rc.1
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b // indirect
golang.org/x/sys v0.0.0-20210611083646-a4fc73990273 // indirect
)
replace (
github.com/Azure/go-autorest => github.com/Azure/go-autorest v13.3.3+incompatible
github.com/goharbor/harbor => ../
google.golang.org/api => google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff
)
//如果你想在当前项目中跳过某个依赖指定的版本,那么就可以将这个版本放入到exclude模块当中,这样当你使用go get时就会自动跳过这个版本了
exclude (
go.etcd.io/etcd/client/v2 v2.305.0-rc.0
go.etcd.io/etcd/client/v3 v3.5.0-rc.0
)
retract (
v1.0.0 // 该版本已被弃用,但是 tag 仍然在,如果你想继续使用,则可以在此指定
)
Go Module 引入语义化版本机制和最小版本选择算法来方便依赖管理
语义化版本机制:
${MAJOR}.${MINOR}.${PATCH}
- MAJOR 当发生不向下兼容的修改时,更改 MAJOR+1
- MINOR 当新增向下兼容的新功能时,MINOR+1
- PATCH 当修复完善原功能后(向下兼容),PATCH+1
语义化版本机制相当于一个版本命名规范,最小版本选择在引入同一依赖的多个版本时,基于依赖遵循了语义化版本的规范对版本做选择
在存在向下兼容的情况下,选择当前依赖的所有版本中最低的兼容版本(会向下兼容)
所有版本中最低的兼容版本,实际上就是被依赖的所有版本中最大的版本(大版本相同情况下)
不兼容就拉俩版本呗。
Proxy
假如直接向代码托管平台请求相关包会怎样?
- 无法保证构建稳定性 发布者可能对相关文件进行增删改
- 无法保证依赖可用性 平台上的原文件被删除
- 增加第三方压力
因此 Go Proxy 出现了,他类似于一个官方代理兼中间缓存仓库,极大缓解了第三方平台的压力,同时为不同版本的包提供了一个稳定的集中管理环境
go get/go mod
go get 主要用于拉取下载
go mod 常用的就 init、download 和 tidy