GO语言工程实践课后作业:实现思路、代码以及路径记录 | 青训营

51 阅读6分钟

并发编程

并发 是多线程程序在一个核的cpu上运行

 

image.png

 

并行 是多线程程序在多个核的上运行

 

 

image.png

 

Go可以充分发挥多核优势,高效运行

一个重要概念

 

协程

协程的开销比线程小,可以理解为轻量级的线程,一个Go程序中可以创建上万个协程。

Go 中 开启协程 非常简单,在函数前面增加一个 go 关键字就可以为一个函数开启一个协程。

 

CSP 与 Channel

CSP(Communicating Sequential Process)

 

Go 中提倡通过 通信共享内存 而不是通过共享内存而实现通信

 

那么如何通信呢,通过 channel

 

Channel

语法: make(chan 元素类型, [缓冲大小])

 

无缓冲通道 make(chan int)

有缓冲通道 make(chan int, 2)

这个图就非常的生动形象~

 

 

 

以下是一个例子:

 

第一个协程 作为生产者发送0~9 到 src中

第二个协程 作为消费者计算 src 中每个数的平方发送到 dest 中

主线程输出 dest 中每个数

 

image.png

可以看到每次都会是顺序输出,代表着Go是 并发安全的

 

Go 语言也保留了共享内存的做法,使用sync进行同步,如下

 

image.png

image.png

 

ps:试了好多次都没冲突,乐。把运算稍微改复杂一点就有冲突了

 

image.png

依赖管理

任何大型项目开发都绕不开依赖管理,Go中的依赖主要经历了 GOPATH -> Go Vendor -> Go Module的演变 而现在主要采用Go Module的方式

 

不同环境依赖的版本不同,所以如何控制依赖库的版本?

GOPATH

项目代码直接依赖src下的代码

通过 go get 下载最新版本的包到src目录下

这样的话,就会出现一个问题:无法实现多版本的控制(A、B依赖于同一个包的不同版本,寄)

 

Go Vender

项目目录下新增 vendor文件,所有依赖包副本形式放在其中

通过 vendor => GOPATH 的方式曲线救国

ps:感觉挺像前端的package.json……依赖问题真是绕不过去

 

这又产生了新的问题:

 

无法控制依赖的版本

更新项目时可能出现依赖冲突,从而导致编译出错

Go Module

通过 go.mod 文件管理依赖包版本

通过 go get/go mod 指令工具管理依赖包

达成了终极目标:既能定义版本规则,又能管理项目依赖关系

 

可以类比一下Java中的Maven

 

依赖配置 go.mod

依赖标识语法:模块路径+版本来进行唯一标识

 

[Module Path][Version/Pseudo-version]

 

image.png

如上,需要注意的是:

 

主版本2+的模块会在路径后增加/vN后缀

对于没有go.mod文件且主版本2+的依赖,会 +incompatible

依赖的版本规则分为语义化版本和基于commit的伪版本

语义化版本

格式为:MAJOR.{MAJOR}.{MINOR}.${PATCH} V1.3.0、V2.3.0、 ……

 

不同的 MAJOR 版本表示是不兼容的API

即使是同一个库,MAJOR 版本不同也会被认为是不同的模块

MINOR 版本通常是新增函数或功能,向后兼容

而 PATCH 版本一般是 修复 bug

基于commit的版本

格式为:${vx.0.0-yyyymmddhhmmss-abcdefgh1234}

 

版本前缀是和语义化版本一样的

时间戳 (yyyymmddhhmmss),也就是提交 Commit 的时间

校验码 (abcdefgh1234), 12 位的哈希前缀

每次提交 commit 后 Go 都会默认生成一个伪版本号

 

小测试

 

image.png

如果X项目依赖了A、B两个项目,且A、B分别依赖了C项目的v1.3、v1.4两个版本,依赖图如上,最终编译时所使用的C项目的版本为如下哪个选项?(单选)

 

A. v1.3

B. v1.4

C. A用到c时用v1.3编译,B用到c时用v1.4编译

 

 

 

 

 

 

 

答案为:B 选择最低的兼容版本

依赖分发

这些依赖去哪里下载呢?就是依赖分发

 

在github等代码托管系统中对应仓库上下载?

 

github是比较常见给的代码托管系统平台,而Go Modules 系统中定义的依赖,最终可以对应到多版本代码管理系统中某一项目的特定提交或版本

 

对于 go.mod 中定义的依赖,可以从对应仓库中下载指定软件依赖,从而完成依赖分发。

 

问题也有:

 

无法保证构建确定性

软件作者直接修改软件版本,导致下次构建使用其他版本的依赖,或者找不到依赖版本

无法保证依赖可用性

软件作者直接代码平台删除软件,导致依赖不可用

增加第三方代码托管平台压力。

通过Proxy方式来解决以上问题

 

Go Proxy 是一个服务站点,它会缓存源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用

 

使用 Go Proxy 之后,构建时会直接从 Go Proxy 站点拉取依赖。

 

Go Modules通过 GOPROXY 环境变量控制如何使用 Go Proxy

 

服务站点URL列表,direct表示源站:GOPROXY="proxy1.cn, proxy2.cn,direct"

 

GOPROXY是一个 Go Proxy 站点URL列表,可以使用 direct 表示源站。整体的依赖寻址路径,会优先从 proxy1 下载依赖,如果 proxy1 不存在,就下到 proxy2寻找,如果proxy2 也不存在则会回源到源站直接下载依赖,缓存到 proxy 站点中。

 

工具

 

image.png

 

image.png

测试

测试一般分为回归测试、集成测试、单元测试,从前到后覆盖率逐层变大,成本却逐层降低,所以单元测试的覆盖率一定程度上决定这代码的质量。

 

回归测试一般是QA同学手动通过终端回归一些固定的主流程场景

集成测试是对系统功能维度做测试验证

单元测试测试开发阶段,开发者对单独的函数、模块做功能验证

单元测试主要包括:输入、测试单元、输出以及校对

 

单元的概念较广,包括接口,函数,模块等,用最后的校对来保证代码的功能与我们的预期相符

 

单元测试有以下几点好处

 

保证质量

整体覆盖率足够时下,既保证了新功能正确性,又未破坏原有代码的正确性

提升效率

代码有bug的情况下,通过单测,可以在一个较短周期内定位和修复问题

Go中的单元测试有以下规则:

 

所有测试文件以 _test.go 结尾

func TestXxx(testing.T)

初始化逻辑放到 TestMain函数中(测试前的数据装载配置、测试后的释放资源等)

例子:

 

main.go

 

image.png

main_test.go

 

image.png

 

image.png

在实际项目中,单测覆盖率

 

一般项目的要求是50%~60%覆盖率

对于重要的资金型服务,覆盖率可能要求达到80%

单测需要保证稳定性和幂等性

 

稳定是指相互隔离,能在任何时间,任何环境,运行测试

幂等是指每一次测试运行都应该产生与之前一样的结果

而要实现这一目的就要用到mock机制。

 

bouk/monkey: Monkey patching in Go

 

monkey是一个开源的mock测试库,可以对method,或者实例的方法进行mock,反射,指针赋值Mockey Patch 的作用域在 Runtime,在运行时通过 Go 的 unsafe 包,能够将内存中函数A的地址替换为运行时函数B的地址,将待打桩函数的实现跳转。

 

Go 语言还提供了基准测试框架

 

基准测试是指测试一段程序的运行性能及耗费 CPU 的程度。

而我们在实际项目开发中,经常会遇到代码性能瓶颈问题,为了定位问题经常要对代码做性能分析,这就用到了基准测试。使用方法类似于单元测试