这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记
1.语言进阶
1.1并发VS并行
- 并发:多线程程序在一个核的CPU上运行
- 并行:多线程程序在多个核的CPU上运行
- go语言可以充分发挥多核的优势,为并发而生
1.2协程VS线程
- 协程:用户态,轻量级线程,栈KB级别
- 线程:内核态,线程跑多个协程,栈MB级别
1.3 CSP(Communicating Sequential Processes)
- 提倡通过通信共享内存而不是通过共享内存而实现通信
1.4 Channel
- 这里主要是介绍了Channel,有缓冲、无缓冲、引用类型这些
1.5 Lock
- 展示了Lock和WaitGroup在并发的情况下起到的作用
package concurrence
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 10; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock:", x)
x = 0
for i := 0; i < 10; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock:", x)
}
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
2.依赖管理
2.1 GOPATH、Go Vendor、Go Module简介
- 不同环境(项目)依赖的版本不同
- 控制依赖库的版本
-
GOPATH,环境变量,GO项目的一个工作区
-
结构
-
bin 项目编译的二进制文件
-
pkg 项目编译的中间产物,加速编译
-
src 项目源码
- 项目代码直接依赖src下的代码
- go get下载最新版本的包到src目录下
-
-
弊端
-
无法实现package的多版本控制
-
-
-
Go Vendor
-
项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
-
现在vendor下寻找依赖,再向GOPATH寻找
-
通过每个项目引入一份依赖的副本来解决多项目需要同一个package依赖的冲突问题
-
弊端
- 无法控制依赖的版本
- 更新项目有可能出现依赖冲突,导致编译出错
-
-
Go Mudule
- 通过go.mod文件管理依赖包版本
- 通过go get/go mod指令工具管理依赖包
-
依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库Proxy
- 本地工具 go get/mod
2.2 示例分析
-
go mod
-
version
- 语义化版本 {MINOR}.${PATCH} 例如 V1.3.0
- 局域commit伪版本 vX.0.0-yyyymmddhhmmss-abcdefg1234
-
关键字
-
indirect
-
incompatible
-
2.3 依赖分发
GOPROXY="proxy1.cn,https://proxy2.cn,…"
服务站点URL列表,“direct”表示源站,如果前面的代理都找不到的话,那么将会“回源”
2.4 工具
-
go get
-
go mod
3.测试
3.1 单元测试
-
组成:输入、测试单元、输出、与期望校对
-
作用:保证质量,提升效率
-
规则:
- 所有测试文件以_test.go结尾
- 函数func TestXxx(*testing.T)
- 初始化逻辑放到func TestMain (m *testing.M)中
-
可以用一些外部的assert库来判断结果
-
单元测试水准的标准——覆盖率
3.2 Mock测试
-
一个常见的Mock测试包,monkey :github.com/bouk/monkey
-
为一个函数打桩
被测试函数
package test import ( "bufio" "os" "strings" ) func ReadFirstLine() string { open, err := os.Open("log") /* log中内容为 line11 line22 line33 */ defer open.Close() if err != nil { return "" } scanner := bufio.NewScanner(open) for scanner.Scan() { //返回第一行 return scanner.Text() } return "" } func ProcessFirstLine() string { line := ReadFirstLine() //替换 destLine := strings.ReplaceAll(line, "11", "00") return destLine }测试函数
package test import ( "bou.ke/monkey" "github.com/stretchr/testify/assert" "testing" ) func TestProcessFirstLine(t *testing.T) { firstLine := ProcessFirstLine() assert.Equal(t, "line00", firstLine) } //这两个函数的结果都是PASS,但是不同的是,上面的那个函数需要依赖于log文件,而下面这个打桩函数不需要依赖于本地文件,用一个新的返回“line11”的函数代替 func TestProcessFirstLineWithMock(t *testing.T) { //对ReadFirstLine函数做一个打桩操作,不依赖本地文件 monkey.Patch(ReadFirstLine, func() string { return "line110" }) //打桩函数的卸载 defer monkey.Unpatch(ReadFirstLine) line := ProcessFirstLine() assert.Equal(t, "line000", line) } -
为一个方法打桩
3.3 基准测试
-
例子:随机选择执行服务器
-
运行
-
测试函数以Benchmark开头
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" ) //BenchmarkSelect-8 70160442 16.90 ns/op func BenchmarkSelect(b *testing.B) { InitServerIndex() b.ResetTimer() for i := 0; i < b.N; i++ { Select() } } //BenchmarkSelectParallel-8 22544683 1.77 ns/op func BenchmarkSelectParallel(b *testing.B) { InitServerIndex() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { Select() } }) } //BenchmarkFastSelectParallel-8 1000000000 1.102 ns/op func BenchmarkFastSelectParallel(b *testing.B) { InitServerIndex() b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { FastSelect() } }) }
测试可以得知fastrand.Intn()的速度更快,但同时也牺牲了一些随机数量的一致性,rand再高并发场景会有性能问题
-
4. 提问
-
协程和用户线程的区别?
答:协程可以理解为用户态的线程,但是为了和内核态的线程做个区分。
-
项目中实现鉴权什么时候用session+cookie的方式,什么时候用jwt token的方式呢?
答:一般和端上是session+cookie,http和服务端交互的时候一般用jwt。
-
go可以充分利用多核的优势,而其他语言类似java,无法充分利用
答:因为go是自己实现了一个用户态线程的调度,而其他语言更多是依赖系统的调度。