这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
并发编程
并发&并行
-
并发
- 并发是多个线程同时进行
- 单核处理多线程的模式下各个线程交替进行
-
并行
- 并行是多个线程同一时刻同时进行
- 多核处理多线程可以进行并行
-
特点:
- 并发不一定并行
- 并行可以充分调动多核计算机的性能
Coroutine
Coroutine:协程
线程:用户态,轻量级线程,栈MB级别
协程:内核态,线程跑多个协程,栈KB级别
CSP
Communicating Sequential Processes
协程间的通信可以有两种方式:
- 通过通信共享内存
- 通过共享内存实现通信
- 在go中提倡的是前者
Channel
通信通道
通信通道有两种:有缓冲通道和无缓冲通道
func main() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
defer close(src)
for i := 0; i < 20; i++ {
fmt.Println(i, "被造成来了!")
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i
}
}()
for i := range dest {
fmt.Println(i, "被消费了!")
}
}
Lock
并行安全
进行通过共享资源通信的时候,应当实现Lock保证并发安全
var(
x int64
lock sync,Mutex
)
func addWithLock(){
for i:= 0; i < 2000;i++{
lock.Lock()
x+=1
lock.Unlock()
}
}
WaitGroup
计数器
当开启线程的时候+1;
执行结束-1;
主线程阻塞知道计数器为0;
func hello(i int) {
fmt.Println("hello goroutine:", i)
}
func main() {
var waitGroup sync.WaitGroup
waitGroup.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer waitGroup.Done()
hello(j)
}(i)
}
waitGroup.Wait()
}
依赖管理
为了更多的关注业务逻辑的实现,我们不可能基于标准库进行0~1编码搭建项目,像是涉及框架、日志、driver、以及collection等一系列依赖都会通过sdk的方式引入,这样对依赖包的管理就显得尤为重要
演进路线
GOPATH->GO Vendor->Go Module
不同环境(项目)依赖的版本不同
因此需要控制依赖库的版本
GOPATH
GOPATH是Go语言支持的一个环境变量
value是Go项目的工作区。 目录有以下结构:
- src:存放Go项目的源码;
- pkg:存放编译的中间产物,加快编译速度;
- bin:存放Go项目编译生成的二进制文件
在这种管理方式下,无法实现package的多版本控制
Go Vendor
Vendor 是当前项目的一个目录,其中存放了当前项目的依赖副本
如果当前项目存在Vendor目录,会优先使用该目录下的依赖,如果依赖不存在会去GOPATH中寻找
寻址:vendor =》 GOPATH
但是此时仍然无法控制依赖的版本,更新项目困难出现依赖冲突
Go Module
- 使用go.mod 文件管理依赖包的版本
- 通过go get/go mod指令工具来管理依赖包
- 类似于Java中的maven
依赖管理的三要素:
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖 Proxy
- 本地工具 go get/mod
依赖配置
go.mod
模块路径标识一个模块,从中可以看到从哪里找到此模块
依赖包的源码由GitHub托管,如果项目的子包向北单独引用,则需要单独的init go.mod 文件进行管理
下面的是原生依赖的sdk的版本
最下面是单元依赖,每个依赖单元由模块路径+版本来唯一标识
Version
-
语义化版本
${MAJOR}.${MINOR}.${PATCH}v1.2.0
-
基于commit伪版本
vx.0.0-yyyymmddhhmmss-abcdefgh1234v0.0.0-20220401081311-c38fb59326b7
indirect
下面我们再来看下依赖单元中的特殊标识符,首先是indirect后缀,表示go.mod对应的当前模块,没有直接导入该依赖模块的包,也就是非直接依赖,标示间接依赖,例如
incompatible
主版本2+模块会在模块路径增加/N后缀
对于没有go.mod文件并且主版本在2或者以上的依赖,会在版本号后加上+incompatible后缀
依赖图
在这图中,编译时使用的C项目的版本为V1.4,因为会选择一个最低版本的均兼容的版本
依赖分发
Proxy
Go Proxy是一个服务站点,使用它之后,构建是会直接从Go Proxy站点拉取依赖
GOPROXY
GOPROXY="https://proxy1.cn,https://proxy2.cn ,direct” GOPROXY=“https://proxy1.cn,https://proxy2.cn,Direct”
服务站点的URL列表,“direct”表示源站
寻址次序为Proxy1-> Proxy2-> Direct
工具
-
go get
-
go mod
测试
单元测试
基本规则
- 所有测试文件以_test.go结尾
- func TestXxx(testing.T)
- 初始化逻辑放到TestMain中
覆盖率
覆盖率表示在测试中由多少比例行数的代码被使用到了
测试分支应当相互独立、全面覆盖
测试单元粒度要小,函数保证单一职责
基准测试
基准测试测定程序运行时的性能以及消耗CPU的程度