这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。
一、Go语言进阶(并发编程)
-
并发与并行
- 并发:多线程程序在cpu的一个核上运行(通过切换时间片的方法实现)
- 并行:多线程程序在cpu的多个核上运行
-
Goroutine
- 线程:内核态,栈为mb级
- 协程:用户态,一个线程上运行多个协程,栈为kb级(无法利用多核资源,本质上是一个线程,即不可实现并行)
在Golang中可使用关键字go开启一个goroutine,例子如下:
func GoRoutine() { for i := 0; i < 5; i++ { go func(j int) { fmt.Println(j) }(i) } } -
CSP(communicating sequential processes)
不同的线程/协程之间需要通信,有两种不同的方法:
- 通过通信共享内存(go中推荐):通过通道传输数据实现通信
- 通过共享内存实现通信:在共享的内存中记录需要通信的内容,为了避免多个线程/协程同时修改一块内存,一般需要进行加锁的操来保证正确
-
Channel
在Golang中创建Channel:
make(chan 元素类型, 缓冲大小)
// 无缓冲 chan1 := make(chan int) // 有缓冲,大小为3 chan2 := make(chan int, 3)- 无缓冲通道:当一个Gorountine向channel中发送一个元素后,channel会堵塞(即其他向该channel发送元素的操作会进入等待),直到channel中的元素被一个Gorountine读走
- 有缓冲通道:当一个Gorountine向channel中发送一个元素后,channel并不会直接堵塞,而是可以在元素尚未被读走的情况下继续向其中发送元素,直到channel内剩余元素个数达到缓冲大小限制才堵塞,这种方法适用于一些特殊情况,比如发送端的发送速度高于接收端的接收速度时
-
并发安全Lock
Channel是通过通信共享内存,是Golang中的推荐做法,但Golang同样可以通过共享内存实现通信
// 定义一个锁 var lock sync.Mutex // 定义一个全局变量 x := 0 func add() { for i := 0; i < 2000; i++ { lock.Lock() x += 1 lock.Unlock() } } func main() { for i := 0; i < 5; i++ { go add() } } -
WaitGroup
不同Gorountine的执行速度并不一样,如果外部主协程过早退出会导致Gorountine没有执行完就退出。我们可以使用
time.Sleep(time.Second)来手动延时来确保所有Gorountine都结束后再退出主协程,但绝大部分时候并不能准确预估Gorountine需要的执行时间,如果延时过多会极其影响使用体验
使用WaitGroup可以很优雅地解决这个问题,WaitGroup实际上是在内存中维护了一个计数器,具体使用如下:
func main() { // 定义一个WaitGroup var wg sync.WaitGroup for i := 0; i < 5; i++ { // 计数器+1 wg.add(1) go func() { // 一个Gorountine已完成,计数器-1 defer wg.Done() /* operation */ }() } // 等待,直到计数器归零 wg.Wait() }
二、依赖管理
Golang的依赖管理有GOPATH、Go Vendor和Go Module三种,Go Module使用最多,因此只对Go Module作详细介绍
-
Go Module:
- go.mod文件管理依赖包版本
- go get/go mod指令工具管理依赖包
-
依赖管理三要素
- 配置文件描述依赖:go.mod
- 中心仓库管理依赖库:Proxy
- 本地工具:go get/mod
-
工具-go mod
go mod init 初始化,创建go.mod文件
go download 下载模块到本地缓存
go mod tidy 自动增加需要的依赖,删除不需要的依赖(最常用)
三、测试
-
单元测试
-
测试文件
测试文件以_test.go结尾
-
测试函数
func TestXxx(*testing.T){}
指令: go test[flags] [packages]
-
初始化逻辑放到TestMain中
func TestMain(m *testing.M) {}
-
通过assert.Equal()得到测试的覆盖率
使用指令:go test Xxx_test.go Xxx.go --cover
-
-
Mock测试
- 使用开源项目github.com/bouk/monkey
- 快速Mock函数可以为一个函数或者方法打桩,不依赖本地文件
-
基准测试
-
测试函数
func BenchmarkXxx(b *testing.B)
-
使用指令 go test -bench=.
-