1 课程资料
导学: 【Go 语言原理与实践学习资料】第三届字节跳动青训营-后端专场 - 掘金
项目: GitHub - Moonlight-Zhao/go-project-example at V0
PPT: Go语言入门-工程实践
2 并发
2.1 Goroutine
使用 go f(s) 在一个协程中调用这个函数。 协程并发执行。
package concurrence
import (
"fmt"
"time"
)
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)
}
这里启动了5个协程并发调用hello函数。
使用WaitGroup来阻塞主协程,可以等待所有子协程执行完。常用方法如下:
| 方法名 | 功能 |
|---|---|
| (wg * WaitGroup) Add(delta int) | 等待组的计数器 +1 |
| (wg * WaitGroup) Done() | 等待组的计数器 -1 |
| (wg * WaitGroup) Wait() | 当等待组计数器不等于 0 时阻塞直到变 0。 |
package concurrence
import (
"fmt"
"time"
"sync"
)
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
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()
//time.Sleep(time.Second)
}
2.2 CSP并发模型
2.3 Channel
使用 make(chan val-type) 创建一个新的通道
无缓冲(默认):只有对应的接收(<- chan) 通道准备好接收时,才允许进行发送(chan <-)。
有缓冲:允许在没有对应接收者的情况下,缓存一定数量的值
package concurrence
func CalSquare() {
src := make(chan int) //无缓冲
dest := make(chan int, 3) //有缓冲
go func() {
defer close(src)
for i := 0; i < 10; i++ {
//使用 channel <- 语法 发送 一个新的值到通道中
//使用 <-channel 语法从通道中 接收 一个值
src <- i
}
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
//复杂操作
println(i)
}
}
2.4 锁
锁和其他语言的用法类似,这里就不赘述了
var (
x int64
lock sync.Mutex // 定义锁
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock() // 加锁
x += 1
lock.Unlock() // 解锁
}
3 依赖管理
3.1 go mod
modules是源代码交换和版本控制的单元.
默认下,以下情况启用module功能。这
- 当前目录在GOPATH/src之外且该目录包含go.mod文件
- 当前文件在包含go.mod文件的目录下面
使用go mod init 初始化生成go.mod 文件,执行 go run server.go 运行代码会发现 go mod 会自动查找依赖自动下载,包的版本需要升级可以使用go get系列语句。go mod tidy清理未使用的module,下载所需的包,go list -m all列出当前module和所有依赖。
3.2 依赖分发
Go Modules系统中定义的依赖,最终可以对应到多版本代码管理系统中某一项目的特定提交或版本,对于go.mod中定义的依赖,则直接可以从对应仓库中下载指定软件依赖,从而完成依赖分发。但直接使用版本管理仓库下载依赖,存在多个问题。
go proxy应运而生,Go Proxy是一个服务站点,它会缓源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用,从而实现了提供"immutability"和"available"的依赖分发;使用Go Proxy之后,构建时会直接从Go Proxy站点拉取依赖。
Go Modules通过GOPROXY环境变量控制如何使用Go Proxy; GOPROXY是一个Go Proxy站点URL列表,可以使用"direct"表示源站。对于示例配置,整体的依赖寻址路径,会优先从proxy1下载依赖,如果proxy1不存在,后下钻proxy2寻找,如果proxy2,中不存在则会回源到源站直接下载依赖,缓存到proxy站点中。
Go proxy 设置链接分享:www.jianshu.com/p/99aa7522c…
4 测试
4.1 单元测试
Go 语言推荐测试文件和源代码文件放在一块,测试文件以 _test.go 结尾
example/
|--calc.go
|--calc_test.go
测试函数格式为:”func TestXxx(t *testing.T)“
package concurrence
import "testing"
func TestCalSquare(t *testing.T) {
CalSquare()
}
进行测试之前需要初始化操作(例如打开连接),测试结束后,需要做清理工作(例如关闭连接)等等。这个时候就可以使用TestMain()。
4.2 mock测试
如果文件被修改或者删除测试就会fail。为了保证测试case的稳定性,我们对读取文件函数进行mock,屏蔽对于文件的依赖。
monkey是一个Go单元测试中十分常用的打桩工具,指针赋值Mockey Patch的作用域在Runtime, 在运行时通过通过Go的unsafe包,能够将内存中函数的地址替换为运行时函数的地址。这样就可以不对内存中的文件有强依赖。
为一个函数打桩:用一个函数A去替换原函数B,A就是一个打桩函数。
安装
go get bou.ke/monkey
func TestProcessFirstLineWithMock(t *testing.T) {
// 通过patch对Readfineline进行打桩mock,默认返回line110
monkey.Patch(ReadFirstLine, func() string {
return "line110"
})
// 通过defer卸载mock,这样整个测试函数就摆脱了本地文件的束缚和依赖。
defer monkey.Unpatch(ReadFirstLine)
line := ProcessFirstLine()
assert.Equal(t, "line000", line)
}
4.3 基准测试
Go语言标准库内置的testing测试框架提供了基准测试(benchmark)的能力,有利我们进行代码的性能优化
基准测试以Benchmark开头,入参是testing.B,用b中的N值反复递增循环测试(对一个测试用例的默认测试时间是1秒,当测试用例函数返回时还不到1秒,那么testing.B中的N值将按1、2、5、10、20、50递增,并以递增后的值重新进行用例函数测试。)
Resttimer重置计时器,我们再reset之前做了init或其他的准备操作,这些操作不应该作为基准测试的范围;
runparallel是多协程并发测试;执行2个基准测试,发现代码在并发情况下存在劣化,主要原因是Select()函数中调用了rand,rand为了保证全局的随机性和并发安全,持有了一把全局锁。fastrand在高并发则会效率更高,具体可以自己搜索了解一下
package benchmark
import (
"github.com/bytedance/gopkg/lang/fastrand"
"math/rand"
)
var ServerIndex [10]int
func InitServerIndex() {
for i := 0; i < 10; i++ {
ServerIndex[i] = i+100
}
}
func Select() int {
return ServerIndex[rand.Intn(10)]
}
func FastSelect() int {
return ServerIndex[fastrand.Intn(10)]
}
package benchmark
import (
"testing"
)
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()
}
})
}