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

125 阅读4分钟

1.Go语言进阶

1.1 并发与并行

并发指多个线程在单核CPU上运行,是逻辑上的“同时”,某一时间点,实际上只有一个线程运行; 并行指多个线程在多核CPU上运行,可以存在某一时间点,多个不同的线程同时运行。

1.2 Goroutine

Go中一个重要概念——协程(Goroutine)。协程可以理解为轻量级的线程,协程的调度有Golang在运行时管理,一个线程可以跑多个协程。协程栈为KB级别,线程栈级别为MB。 Golang中使用go关键字开启协程,同一个程序中的所有 Goroutine 共享同一个地址空间。

package main

import (
	"fmt"
	"time"
)

func main() {
	for i:= 0; i < 5; i++ {
		go func (j int) {
			hello(j)
		}(i)
	}
	time.Sleep(time.Second) //保证子协程执行完之前,主协程(即main)不退出
}

func hello(i int) {
	fmt.Println("hello goroutine: " + fmt.Sprint((i)))
}
//每次运行结果为乱序输出
//output1:
//hello goroutine: 0
//hello goroutine: 1
//hello goroutine: 4
//hello goroutine: 2
//hello goroutine: 3
//output2:
//hello goroutine: 4
//hello goroutine: 1
//hello goroutine: 3
//hello goroutine: 2
//hello goroutine: 0
//......
//outputN:
//hello goroutine: 0
//hello goroutine: 4
//hello goroutine: 1
//hello goroutine: 2
//hello goroutine: 3

1.3 CSP(Communicating Sequential Processes)

Golang提倡通过通信共享内存而不是通过共享内存实现通信。实现通信共享内存涉及到通道(Channel)的概念,假设有两个协程G1和G2,G1通过通道发送数据到G2共享信息。

1.4 通道(CHannel

Golang中使用make(chan type, cacheSize)创建通道,cacheSize表示通道中缓存大小,如果没有,则表示该通道无缓冲。操作符<-用来实现通道的数据传输。for循环可用来遍历通道。

package main

import (
	"fmt"
)

func main() {
	src := make(chan int) //发送者
	dst := make(chan int, 5) //接收者
	NegtiveNum(src, dst)
	for i:= range dst { // 从通道dst中取出取负数后的数并打印
		fmt.Println(i)
	}
}

func NegtiveNum(src chan int, dst chan int) { //将1~5取反
	go func () {
		defer close(src) 
		for i:=1; i<=5; i++ {
			src <- i //将i发送到通道src
		}
	}()
	go func () {
		defer close(dst) 
		for i:= range src { //遍历通道
			dst <- (-i) // 从通道src中取出i,取负数传入dst通道
		}
	}()
}

1.5 并发安全Lock

Golang中使用互斥锁(也叫排他锁)sync.Mutex对不同的协程加锁与解锁,防止多个协程在访问同一内存区时出现混乱。 为什么会出现混乱?假设有两个协程G1和G2都做同一个变量sum的加1运算,当G1获取当前sum的值并加1,此时G2也获取sum的值,但是如果G1还没有把计算后的结果写入,G2获得的sum值就为原始的值,造成两个协程都写入了相同的结果,这样在很多实际情况下不符合业务逻辑。

lock sync.Mutex //声明一个互斥锁
lock.Lock() //加锁
lock.Unlock() // 解锁

1.6 WaitGroup

WaitGroup也在Golang中的sync包下,WaitGroup能实现多个任务的同步,保证在并发环境中完成指定数量的任务。WaitGroup涉及的方法如下:

方法名功能
(wg * WaitGroup) Add(delta int)计数器+delta
(wg * WaitGroup) Done()计数器-1
(wg * WaitGroup) Wait()计数器!=0时阻塞直到为0

2.依赖管理

实际开发过程中,只有标准库往往不能满足开发需求,Golang中使用sdk方式引入库,依赖管理从GOPATH到Go Vender再到Go Module演变,现在多使用Go Module管理库。依赖管理三要素包括配置文件go.mod, 中心仓库管理依赖库proxy, 本地工具go get/mod。

2.1 GOPATH

GOPATH分为三个文件夹bin、pkg和src管理项目,bin保存项目编译的二进制文件,pkg保存项目编译的中间产物,src保存项目源码,所有的项目代码直接依赖到src下的代码。GOPATH的弊端在于无法实现package的多版本控制。

2.2 Go Vender

项目目录下增加Vender文件,所有的依赖包以副本形式放在vender,go代码会优先从vendor目录先寻找依赖包;找不到再从GOPATH 中寻找。弊端在于无法精确的引用 外部包进行版本控制,不能指定引用某个特定版本的外部包,只是在开发时将其拷贝过来,但是一旦外部包升级,vendor 下面的包会跟着升级,而且 vendor 下面没有完整的引用包的版本信息, 对包升级带来了无法评估的风险。

2.3 Go Module

  • 通过go.mod文件管理依赖包版本
  • 通过go get/go mod指令工具管理依赖包
  • go.mod文件记录了项目所有的依赖信息,其结构大致如下:
module example

go 1.20

require (
 github.com/gin-gonic/gin v1.4.0
 github.com/go-sql-driver/mysql v1.4.1
 github.com/jmoiron/sqlx v1.2.0
 github.com/satori/go.uuid v1.2.0
)

2.4 依赖配置

2.4.1 version
  • 语义化版本:$(MAJOR).S{MINOR.S[PATCH}
  • 基于commit伪版本:vX.0.0-yyyymmddhhmmss-abcdefgh1234
2.4.2 indirect和incompatible

indirect表示非直接依赖,incompatible表示允许不同主版本(MAJOR)的不兼容

3.测试

3.1 单元测试

单元测试流程如下,测试单元中包含不同的模块、函数等等

graph TD
输入 --> 测试单元 --> 输出
输出 --> 校对
期望输出 --> 校对

Golang中测试命令为go test,有以下需要注意的点:

  • 所有测试文件以_test.go结尾
  • 测试函数格式为func TestXxx(*testing.T)
  • 初始化逻辑放在TestMain()
  • 通过覆盖率判断单元测试是否合格,参数为--cover
  • 测试分支相互独立,测试单元粒度足够小(函数单一职责)

image.png

3.2 Mock测试

单元测试可能涉及到外部依赖,导致不稳定,这时候需要Mock测试。使用开源库Monkey打桩工具实现Mock。Mock将目标函数或方法的实现跳转到桩实现,避免依赖本地文件,注意事项有:

  • Monkey不支持内联函数,在测试时go test xxx -gcflags=-l禁用内联。
  • Monkey不是线程安全的。
  • Monkey使用monkey.Patch(<target function>, <replacement function>)monkey.Unpatch(<target function>)函数为测试单元打桩和解除桩。

3.3 基准(Benchmark)测试

基准测试的目的的是对代码的性能进行性能分析,Golang中提供了内置测试框架对代码进行基准测试。测试函数以BenchmarkXxx开头表示基准测试,命令为go test -bench=$dir$

import (
   "fmt"
   "testing"
 )
 func BenchmarkSprint(b *testing.B) {
   b.ResetTimer()
   for i := 0; i < b.N; i++ {
     fmt.Sprint(i)
   }
 }

总结

Golang进阶学习中,个人觉得最重要的是协程的概念,go中协程的调度和管理是实现高并发高性能的重要一步,对于协程的通信提倡使用通道进行数据的传输,在sync包下实现了锁等机制,防止数据错乱;其次是项目的依赖管理,主要使用go get/mod对外部依赖包管理;不同的测试都向着高性能高稳定的方向进行,测试使用go test命令。