Go并发编程
前置知识
并发
多线程程序在一个核的cpu上运行
并行
多线程程序在多个核的cpu上同时运行
Goroutine Go实现并发的工具
协程是用户态的轻量级线程,所用栈为KB级别 线程是内核态的,一个线程可以跑多个协程,所用栈为MB级别 具体实现已经在上一篇中有所设计
协程间的通信 Channel
通过通信共享内存
make(chan 元素类型, [缓冲大小])
使用实例
func calSquare() {
src := make(chan int)
dest := make(chan int,2)
go func() {
defer close(src)
for i := 0;i < 10; i += 2 {
src <- i
}
}()
go func() {
defer close(dest)
for i := range(src) {
dest <- i * i
}
}()
for i := range(dest) {
fmt.println(i)
}
}
并发安全
var(
x int
lock sync.Mutex
)
func calWithLock() {
for i :=0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func calWithoutLock() {
for i :=0; i < 2000; i++ {
x += 1
}
}
func test() {
x = 0
for i := 0; i < 5;i++ {
go calWithLock()
}
time.Sleep(time.Second)
fmt.Println("cal with lock: ",x)
x = 0
for i := 0; i < 5;i++ {
go calWithoutLock()
}
time.Sleep(time.Second)
fmt.Println("cal without lock: ",x)
}
并发同步 WaitGroup
WaitGroup中存在有三个方法来实现对并发任务的计数
Add(delta int)、Done()、Wait()
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(delta 5)
for i := 0;i < 5;i++ {
go func(j int) {
defer wg.Done()
fmt.Println(j)
}
}
wg.Wait()
}
依赖管理
Go 依赖管理演进
GOPATH -> GO Vendor -> Go Module
GOPATH
即为Go的环境变量,编译所需的源文件和编译结果都会保存在这个目录下。使用go get获取的包也会存在子目录/src下。
存在的问题,由于在不同的项目中可能需要用到不同版本的包,使用同一环境就会带来版本管理的困难。
Go Vendor
在项目目录下新增文件夹./vendor。所有依赖包的副本都会保存在这个目录下。程序在编译执行的时候也会优先在这个文件夹里搜寻所用到的包。
存在的问题 实际上是依赖于包的源码,无法控制依赖的版本。更新项目可能出现依赖冲突。
Go Module
定义版本规则和管理项目依赖关系
通过go.mod管理依赖版本
go mod init //创建go.mod
go mod download //下载所需依赖
go mod tidy //对依赖进行整理,下载需要的依赖,删除不要的依赖
通过go get/go mod指令工具管理依赖包
依赖分发-Proxy
为了避免直接从代码平台抓取依赖,同时保证所需依赖的长期稳定可用。因此引入了Proxy。Proxy会缓存所用过的依赖,包括各种版本。类似于加缓存的方法。 配置:
GOPROXY="https://proxy1.cn, https://proxy2.cn, direct"
测试
回归测试
集集成成测测试试
单单单元元元测测测试试试
从上到下,覆盖面逐渐提升,同时成本也随之降低。
单元测试
执行方式go test [flag][package]
-cover 查看覆盖率,对测试用例覆盖代码的情况进行检查
-v 显示每个用例的测试结果
-run TestFuncName 运行某一个用例
./example/
|--func.go
|--func_test.go
// ./example/func.go
package attention
func HelloTom() string {
return "Jerry"
}
// ./example/func_test.go
package attention
import {
"testing"
"github.com/stretchr/testify/assert"
}
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectedOutput := "Tom!"
// 手动写判断语句进行判断
if output!= expectedOutput {
t.Errorf("Expected '%s', but got '%s'", expectedOutput, output)
}
调用assert包进行判断
assert.Equal(t,expectedOutput,output)
}
注意事项
测试单元粒度足够小,函数单一职责 测试分支相互独立、全面覆盖 一般覆盖率在50%~60%
Mock测试
在测试涉及到外部依赖时,可以通过Mock测试模拟外部依赖完整的情况下进行测试,可以降低对测试环境的要求。
基准测试
对程序的执行效率进行测试
func BenchmarkSelect(b *testing.B) {
InitServerIndex()
b.ResetTimer()
for i := 0 ; i< b.N; i++ {
Select()
}
}
func BenchmarkSelectParallel(b *testing.B) {
InitServerIndex()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Select()
}
})
}
import (
"math/rand"
)
var ServerIndex [10]int
func InitServerIndex() {
for i := 0; i < 10; i++ {
ServerIndex[i] = i + 100
}
}
//rand 函数的并发性能问题
//在并行执行的时候,为保证线程安全,rand函数会有一个全局锁,导致并行效率下降。
func Select() int {
return ServerIndex[rand.Intn(10)]
}
//字节开源的快速随机的函数实现
//https://github.com/bytedance/gopkg
func FastSelect() int {
return ServerIndex[fastrand.Intn(10)]
}