GO语言进阶|青训营笔记

74 阅读4分钟

本篇文章内容:

Go语言高性能的本质

Go语言依赖管理的演进路线

单元测试实践

最后的项目练习

一、Go语言的高性能

并发 VS 并行

并行:多线程程序在一个核的cpu上运行。

并行:多线程程序在多个核的cpu上运行。

01.Goroutine

线程:用户态、轻量级线程,栈 MB 级别。

协程:内核态、线程跑多个协程,栈 KB 级别。

func hello(i int) { Println("hello goroutine : " + fmt.Sprint(i)) } func HelloGoRoutine() { for i := 0; i < 5; i++ {        go func(j int) {            hello(j)       }(i)   } time.Sleep(time.Second) }

02.CSP

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

03.Channel

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

无缓冲通道 make(chan int)

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

04.并发安全 Lock

var (    x    int64    lock sync.Mutex ) ​ func addWithLock() {    for i := 0; i < 2000; i++ {        lock.Lock()        x += 1        lock.Unlock()   } } ​ func addWithoutLock() {    for i:= 0; i < 2000; i++ {        x += 1   } }

05.WaitGroup

计数器: 开启协程+1;执行结束-1;主协程阻塞直到计数器为0.

func ManyGoWait() { var wg sync.WaitGroup wq.Add(5) for i := 0; i < 5; i++ { go func(j int) { defer wg.Done() hello(j) }(i) } wg.Wait() }

首先通过add方法,对计数器+5,然后开启协程,每个协程执行完后,通过done对计数器减少1,最后wait主协程阻塞,计数器为0退出主协程,右边是最终的输出结果

二、依赖管理

对于实际工程不可能基于标准库0~1编码搭建,而其他涉及框架、日志、driver、以及collection等一系列依赖都会通过sdk的方式引入,这样对依赖包的管理就显得尤为重要。

而Go的依赖管理主要经历3各阶段:GOPATH —> Go Vendor —> Go Module

GOPATH

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

目录结构分为:

src:存放Go项目的源码;

pkg:存放编译的中间产物,加快编译速度;

bin:存放Go项目编译生成的二进制文件。

GOPATH的弊端:依赖于某一版本,无法实现多版本控制。

Go Vendor

项目目录下增加 vendor 文件,所有依赖包副本形式放在 $ProjectRoot/vendor

依赖寻址方式:vendor => GOPATH

通过每个项目引入一份依赖的副本,解决了多个项目需要同一个 package依赖的冲突问题。

Go Vendor的弊端:无法控制依赖的版本,更新项目又可出现依赖冲突。

Go Module

Go Modules是Go语言官方推出的依赖管理系统。

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

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

依赖管理三要素

1.配置文件,描述依赖 go.mod

2.中心仓库管理依赖库 Proxy

3.本地工具 go get/mod

依赖配置 - go.mod

 每个单元依赖用模块路径+版本来唯一标示

依赖配置 - version

语义化版本 {MINOR}.${PATCH} v1.3.0 v2.3.0

依赖配置-incompatible

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

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

依赖分发-回源

无法保证构建稳定性:增加/修改/删除软件版本

无法保证依赖可用性:删除软件

增加第三方压力:代码托管平台负载问题

依赖分发-Proxy

Go Proxy是一个服务站点,实现了供“immutability”和“available”的依赖分发;使用 Go Proxy 之后,构建时直接从 Go Proxy 站点拉取依赖。

依赖分发-变量 GOPROXY

GOPROXY是一个 Go Proxy 站点URL列表,可以使用“direct”表示源站。

工具-go get

go get example.org/pkg

@update 默认

@none 删除依赖

@v1.1.2 tag版本,语义版本

@23dfdd5 特定的commit

@master 分支的最新commit

go mod

init 初始化,创建go.mod文件

download 下载模块到本地缓存

tidy 增加需要的依赖,删除不需要的依赖。

三、测试

单元测试主要包括,输入、测试单元、输出、以及校对、单元的概念比较广,包括接口、函数、模块等;用最后的校对来保证代码的功能与我们的预期相符;单侧一方面可以保证质量,在整体覆盖率足够的情况下,一定程度上既保证了新功能本身的正确性,又未破坏原有代码的正确性,另一方面可以提升效率,在代码有bug的情况下,通过编写单测,可以在一个较短周期内定位和修复问题。

单元测试-规则

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

func TestXxx(*testing.T)

初始化逻辑放到 TestMain 中

单元测试-例子

func TestHelloTom(t *testing.T) { output := HelloTom() expectOutput := "Tom" if output ≠ expectOutput { t.Errorf("Expected %s do not match actual %s", expectOutput,output) } }

单元测试-Tips

一般覆盖率:50%~60%,较高覆盖率80%+。

测试分支相互独立、全面覆盖。

测试单元粒度足够小,函数单一职责。

单元测试-依赖

外部依赖 => 稳定&幂等

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

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

单元测试-Mock

monkey是一个开源的mock测试库,可以对method,或者实例的方法进行mock。

快速Mock函数

//Patch将函数替换为另一个函数 func Patch(target, replacement interface{}) *PatchGuard { t := reflect.ValueOf(target) r := reflect.ValueOf(replacement) patchValue(t, r) return &PatchGuard{t, r} } func Unpatch(target interface{}) bool { return UnpatchValue(reflect.ValueOf(target)) }

基准测试

基准测试是指测试一段程序的运行性能及耗费 CPU 的程度。在实际项目开发中,会遇到代码性瓶颈才会使用。

优化代码,需要对当前代码分析

内置的测试框架提供了基准测试的能力

四、项目实战

青训营话题页forum.juejin.cn/youthcamp/p…

需求:社区话题页面

实现一个展示话题(标题,文字描述)和回帖列表的后端http接口。

本地文件存储数据(仅实现一个本地web服务即可)。

话题和回帖数据用文件存储,本地id生成需要保证不重复、唯一性。

组件及技术点

Web框架-Gin

了解go web框架的简单使用

分层结构设计

了解分层设计的概念

文件操作:读文件pkg.go.dev/io

数据查询:索引www.baike.com/wikiid/5527…