GO语言入门-工程实践课程| 青训营笔记

90 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

课程内容

  • 语言进阶
  • 依赖管理
  • 测试

语言进阶

Goroutine-协程

GO语言可以充分发挥多核优势,提高运行效率,防止出现所谓“一核有难 N核围观”的情况。 这要部分归功于Goroutine 也就是 协程;按课程所讲,线程运行在内核态下,一个线程跑多个协程,栈属于MB级别,而协程运行在用户态下,属于轻量级的线程,栈属于KB级别。
通过 go 关键字即可开启,例如:

package concurrence

import (
	"fmt"
	"sync"
)

func hello(i int) {
	println("hello world : " + fmt.Sprint(i))
}

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

这就是一个启动多个协程快速打印hello的例子,其中Add()是增加计数器,Done()是减少,Wait()是挂起直到计数器归0,这是syncWaitGroup的函数,它在其中维护了一个计数器.

CSP(Communicating Sequential Processes)

Go提倡:通过通信达到共享内存的效果,而不是通过共享内存达到通信效果;

Channel-通道

CSP.png
图中的通道就是 channel 它是用来传递数据的一个数据结构,用于在两个 goroutine 之间传递一个指定类型的值来实现同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

channel <- value    // 把 v 发送到通道 ch
value := <-channel  // 从 ch 接收数据
           // 并把值赋给 v

通过make即可声明一个channel:

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

缓冲大小可以和括号可以不填,这样就是无缓冲的channel,通过close()关闭channel。

Lock-锁

当然,go也保留了通过共享内存实现通信的方式,那就意味着需要对互斥区进行上锁:

package concurrence

import "sync"

var (
	x int64
	lock sync.Mutex
)

func addWithLock(){
	for i := 0 ; i < 2000 ; i++ {
		lock.Lock()
		x += 1
		lock.Unlock()
	}
}

依赖管理

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

go.mod

Go.mod.png
课程中还提到了,如果一个项目依赖的其中两个模块各依赖一个模块的高低版本,会选择能兼容这两个版本的最低模块。

Proxy

实际就是通过依赖分发来实现稳定可靠的下载源

go get/mod

  • go mod 命令
命令作用
go mod init生成go.mod 文件
go mod download下载 go.mod 文件中指明的所有依赖
go mod tidy整理现有的依赖 (拉取缺少的模块,移除不用的模块)
go mod graph查看现有的依赖结构
go mod edit编辑 go.mod 文件
go mod vendor将依赖复制到vendor目录下
go mod verify校验一个模块是否被篡改过
go mod why查看为什么需要依赖某模块

其中go mod download 拉起的模块,结果缓存在 $GOPATH/pkg/mod和 $GOPATH/pkg/sumdb 目录下

  • go install = 用来编译安装本地项目
  • go get = 下载 Go 包 + go install
    当然我一般会用git clone 来拉去GitHub上的项目.

其中go get格式为:

go get {模块名}@{最新版本: latest|分支: master|tag: v0.3.2|hash: 342b2e}

测试

概要

测试是很重要的一环,是开发和正式上线中间的最后一道屏障.测试从上到下分为回归测试集成测试单元测试,覆盖率逐层提高,成步却逐步降低.所以单元测试是十分重要的.

单元测试规则

Go语言中的测试依赖go test命令,此命令是一个按照一定约定和组织的测试代码的驱动程序。

  • 测试文件以_test.go为结尾
  • 测试函数在原函数名称基础前加上Test,参数为t *testing.T
  • 初始化逻辑放入TestMain()

单元测试运行

*_test.go 文件 包含三种类型函数,分别是单元测试,基准测试,示例测试函数

类型前缀格式作用
基础测试Test测试程序的逻辑是否正确
性能测试Benchmark测试程序的性能
示例测试Example为程序提供示例

go test 命令的格式为:

go test [-c] [-i] [build flags] [packages] [flags for test binary]
  • -c : 编译go test成为可执行的二进制文件,但是不运行测试。
  • -i : 安装测试包依赖的package,但是不运行测试。
  • build flags:调用go help build查询相关,这些是编译运行过程中需要使用到的参数,一般设置为空
  • packages:调用go help packages查询相关,这些是关于包的管理,一般设置为空
  • flags for test binary: 有很多,详细的可去查询文档

Mock-打桩测试

因为有些函数会对文件进行增删,为了防止对本地文件形成依赖而对函数或者模块使用的测试方法.

工程中的测试

  • 一般覆盖率在 50%~60% ,较高的会达到80% 此时成本也很大
  • 测试分支相互独立,全面覆盖
  • 测试单元粒度足够小,函数单一负责

个人总结

此次课程贴近工程实践内容,前面的协程和通道讲述了Go的并发编程的基础;中间的依赖管理描述了GO项目工程中经常用到的版本和依赖管理,类似Java中的Maven一样,Go也有专属的依赖管理系统;后面的测试栏目讲解了工程实践中重要的部分,大概介绍了单元测试和基准测试的使用方式以及在工程中的作用,使我受益匪浅。