DAY2 Go 语言进阶 | 青训营笔记

175 阅读3分钟

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 依赖管理详解

依赖管理

Go 语言的依赖管理进化史大致如下:

  • GOPATH
  • Go Vendor
  • Go Module

现在基本使用 Go Module,前两者不如他好用,也可以说他继承了之前包管理手段的优点,摈弃了其他备受诟病的缺陷

GOPATH

早期Golang是使用$GOPATH作为依赖管理的机制,它就相当于一个工作区,依赖下载会存入相应文件夹:

  • bin:一般用于存放可执行文件
  • pkg:存放静态连接库文件
  • src:存放第三方依赖的源码和自己开发的代码

GOPATH 的致命缺陷是它没有对于版本的清晰认知,这是很可怕的。如果编写过 Java,就知道哪怕在 Maven 出现之后对于版本之间的兼容性调试是有多么地狱。而这一切在 GOPATH 中你可能会再次经历一遍……

Go Vendor

由于 GOPATH 初期存在巨大的缺陷,因此 1.5 版本引入了该机制用以优化(主要是多了对包版本的管理)

实际上只是在项目 根目录 下建立了一个 vendor 目录,用于存储当前项目的依赖,因此包引用查询的步骤其实是这样:

  1. 在当前项目根目录中的 vendor 目录中查找
  2. $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 常用的就 initdownloadtidy

参考文章

干货!用大白话告诉你什么是Mock测试-51CTO.COM

Go语言的CSP模型

Go 依赖管理详解 - 掘金 (juejin.cn)