Bootstrap

Go 语言进阶 - 工程进阶

前言: \textcolor{Green}{前言:} 前言:

💞这个专栏就专门来记录一下寒假参加的第五期字节跳动训练营
💞从这个专栏里面可以迅速获得Go的知识

今天的内容包括以下两个内容。关于实践的内容我会在后续发布出来。

01.语言进阶:从并发编程的视角了解Go高性能的本质。

02.依赖管理:了解GO语言依赖管理的演进路线

课程代码相关链接

一、语言进阶

1.并发 VS 并行

image.png

区别:并发不是并行。并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。

1.1 Goroutine(协程)

使用者分配足够多的任务,系统能自动帮助使用者把任务分配到 CPU 上,让这些任务尽量并发运作。这种机制在 Go语言中被称为 goroutine。

下图中的协程的栈是 KB 级别。线程是栈 MB 级别

image.png

如果不要求快速那么我们直接 for 循环即可,快速我们就需要开多个协程进行。

go开启协程非常简单,只需要在函数前面加上 go 关键字。

image.png

1.2 CSP(Communicating Sequential Processes)

image.png

一定要记住
DO NOT COMMUNICATE BY SHARING MEMORY; INSTEAD, SHARE MEMORY BY COMMUNICATING.
“不要以共享内存的方式来通信,相反,要通过通信来共享内存。”

1.3 Channel

image.png

无缓冲通道也被称为同步通道
有缓冲通道的后面数字代表是可以同时存在几个。

image.png

1.4 并发安全 Lock

image.png

并发安全问题是有一定概率导致错误的

1.5 WaitGroup

WaitGroup 暴露了三个方法:Add()、 Done()、 Wait();
image.png

和刚开始是多个协程打印例子。这里我们使用 WaitGroup 实现协程的同步阻塞。

image.png

总结

Groutine(协程):通过高效的调度模型实现高并发操作。
Channel(通道):通过通信实现共享内存。(Go中推荐)
Sync相关关键字(Lock、WaitGroup等):实现并发安全操作和协程间的同步。

二、依赖管理

依赖指的是各种开发包,利用已经封装好的、经过验证的开发组件或工具来提升自己的研发效率。

goMod

背景

像单体函数只需要依赖原生的SDK。实际的工程会相对复杂,不可能基于标准库 0~1 编码搭建,更多我们会关注业务逻辑的实现。而其他的一些依赖像涉及框架、日志、driver 以及 collection 等一系列依赖都会通过 SDK 的方式引入,这样我们对依赖包的管理就非常重要了
image.png

2.1 Go 依赖管理演进

Go 的依赖管理经历了以下 3 个阶段。
image.png

2.1.1 GOPATH

GOPATH 是 GO 语言支持的一个环境变量,value 是 go 项目的工作区。

目录结构:

  • src 存放 Go 项目的源码;
  • pkg:存放编译的中间产物,加快编译速度;
  • bin:存放 GO 项目编译生成的二进制文件

image.png

2.1.1 GOPATH - 弊端

image.png

如图所示:同一个 pkg,有2个版本(V1,V2),A->A(),B->B(),而 src 下只有只有一个版本存在,那么 AB 项目无法保证都能构建编译通过。在 GOPATH 管理模式下,如果多个项目依赖同一个库,则依赖该库是同一份代码,所以不同项目不能依赖同一个库的不同版本,这不能满足我们的项目依赖需求,为了解决这个问题,出现了govendor

2.1.2 Go Vendor

image.png

Vendor 是当前项目中的一个目录,其中存放了当前项目依赖的副本。在 Vendor 机制下,如果当前项目存在 Vendor 目录,会优先使用该目录下的依赖,如果依赖不存在,会从 GOPATH中查找。
但 Vendor 无法很好解决依赖包的版本变动问题和一个项目依赖同一个包的不同版本的问题。

2.1.2 Go Vendor - 弊端

image.png

如图中所示,如果项目A依赖 pkg b和c,而 B 和 C 依赖了 D 的不同版本,通过 Vendor 的管理模式我们不能很好的控制对于D的依赖版本,一旦更新项目,有可能带来依赖冲突,导致编译出错。
Vendor 不能很清晰的标识依赖的版本概念。所以出现了go module

2.1.3 Go Module

image.png

Go Modules 是 Go 官方推出的依赖管理系统,解决了之前依赖管理系统存在的像无法依赖同一个库的多个版本等问题。Go Module 从 Go 1.16默认开启;我们一般都读为 go mod(突然发现这个好熟悉,因为我之前遇到过)。

最终通过 go.mod 文件管理依赖包版本;通过 go get/go mod 指令工具管理依赖包。实现终极目标:定义版本规则和管理项目依赖关系。

2.2 依赖管理三要素

image.png

依赖管理之前学习java的时候一定不陌生,我们会想到 maven

但是在 go 中,依赖管理主要是三要素

  1. 配置文件,描述依赖 go.mod
  2. 中心仓库管理依赖库 Proxy
  3. 本地工具 go get/mod

2.3 依赖配置

2.3.1 依赖配置 - go.mod

image.png

模块路径用来标识一个模块,从模块路径可以看出从哪里可以找到该模块,如果是 github 前缀则表示可以从 Github 仓库中找到该模块,依赖包的源代码由 github 托管,如果项目的子包想被单独引用,则需要通过单独的 init go.mod 文件进行管理。

中间的则是依赖的原生库 sdk 版本。

最下面的是单元依赖,每个依赖单元由模块路径+版本来唯一标识。

2.3.2 依赖配置 - version

image.png

gopath 和 govendor 都是源码副本方式依赖,没有版本规则的概念,而 gomod 为了方便管理则定义了版本规则,分为语义化版本和基于 commit 伪版本;其中 语义化版本包括 ${MAJOR}.${MINOR}.${patch},不同的 MAJOR 版本标识不兼容的API,所以即使是同一个库,MAJOR版本不同也会被认为是不同的模块;MINOR 版本通常是新增函数或功能,向后兼容;而 patch 版本一般是修复bug;
而基于commit的版本包括 vx.0.0-yyyymmddhhmmss-abcdefgh1234,基础版本前缀是和语义化版本一样的;时间戳(yyyymmddhhmmss),也就是提交 Commit 的时间,最后是校验码(abcdef),包含12位的哈希前缀;每次提交 commit 后 GO 都会默认生成一个伪版本号。

2.3.3 依赖配置 - indirect

image.png

indirect 后缀标识 go.mod 对应的当前模块,没有直接导入该依赖模块的包,也就是非直接依赖,表示间接依赖。

2.3.4 依赖配置 - incompatible

image.png

主版本 2+ 模块会在模块路径增加 /vN 后缀,这能让 go module 按照不同的模块来处理同一个项目不同主版本的依赖。由于 go module 是1.11实验性,引入这个规则提出之前已经有一些仓库打上了2或者更高版本的tag。为了兼容这部分仓库,对于没有 go.mod 文件并且主版本在 2 或者以上的依赖,会在版本号后面加上 + incompatible后缀。

之前讲语义化版本中,对于同一个库的不同 major 版本,需要不同的pkg目录,用不同的gomod文件管理。例如:V1版本在gomod在主目录下,而对于V2版本,则单独有V2目录,用另一个gomod文件管理依赖路径,来表名不同 major 的不兼容性。对于有些 V2+tag 版本的依赖包并未遵循这一定义规则,就会打上 incompatible 标志。

2.3.4 依赖配置 - 依赖图

image.png

选择最低的兼容版本

2.3.5 依赖分发 - 回源

image.png

go 的依赖分发就是从哪里下载,如果下载的问题

github 是比较常见的代码托管系统平台,而 go modules 系统中定义的依赖,可以对应到多版本代码管理系统中某一项目的特定提交或版本。此时对于gomod中定义的依赖,可以直接从对应仓库中下载指定软件依赖,从而完成依赖分发。

image.png

我们发现,直接使用版本管理仓库下载依赖,存在一些问题。1.无法保证构建确定性:软件作者可以直接在代码平台增加/修改/删除 软件版本,导致下次构建使用另外版本的依赖或找不到依赖版本。无法保证依赖可用性;2.依赖软件作者可以直接代码平台删除软件,导致依赖不可用;3.大幅增加第三方代码托管平台压力。

2.3.5 依赖分发 - Proxy

image.png

go proxy 可以解决上面提出的这些问题。go proxy 是一个服务站点,它会缓冲源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用,从而实现了供“immutablity”和“available”的依赖分发;使用 go proxy 之后,构建时会直接从 go proxy 站点拉取依赖,

2.3.6 依赖分发 - 变量 GOPROXY

image.png

Go Modules 通过 GOPROXY 环境变量控制如何使用 GO PROXY;

GOPROXY是一个 GO PROXY 站点URL列表,可以使用 direct 表示源站。

对于示例配置,整体的依赖寻址路径,会优先从 proxy1下载依赖,如果 proxy1 不存在,就去 proxy2 寻找,如果 proxy2 中不存在则会回源到源站直接下载依赖,缓存到 proxy 站点中。

2.3.7 工具 - go get

这个是go module 的管理工具下使用的。

image.png

2.3.8 工具 - go mod

image.png

我们要注意,在提交之前执行下 go tidy,减少构建时无效依赖包的拉取。

总结

课程内容是非常切合的,通过前一天的语言基础到今天的语言进阶,了解到了并发和并行的区别,也明白了 Go 中是如何选择进行的。同时依赖管理中也发现了和其他语言类似的地方,当然更多的是不同,发现 go 的依赖配置会有更有趣的地方,对比其他工具便捷。至此我也更加明白学习一门语言不是简单的语法,在此基础上可以优化的内容,同时到这个地步也可以进行下一步网络端的项目进行了。期待接下来的课程学习。

参考链接

Go语言进阶与依赖管理

【后端专场 学习资料一】第五届字节跳动青训营

;