这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
1 go进阶
🤔为什么说Go更加适合高并发场景,其高性能的秘密是什么?
1.1 Goroutine
goroutine是Go语言中的协程实现,由Go运行时(runtime)管理。协程是一种用户态的轻量级线程,协程完全由用户控制而不是被操作系统内核调度。
为什么协程适合高并发场景?
- 线程的栈大小是MB级;协程更加轻量,其栈空间是KB级。更小的栈空间意味着相同场景下可以创建的协程数量更多了。
- 协程控制权完全属于用户,使用协程的程序上下文切换开销极小,有效提高程序运行效率。
- 协程本质还是单线程,因此不需要多线程的锁机制,也正因如此不存在同时写变量的冲突,也不需要对共享资源加锁,仅需在使用前进行资源的状态判断即可。协程避免了多线程竞争,降低了上下文切换的系统开销。
如何开启一个协程呢?在使用的方法之前使用go关键字即可。具体如下,
go func(){
fmt.Println("开启了一个协程!")
}
❓协程怎么就不需要对共享资源加锁呢?它是如何实现共享内存的呢?
1.2 CSP
CSP(Communicating Sequential Processes)就是go协程共享资源还不需要加锁的秘笈。协程之间通过Channel通讯来实现共享数据,同时保留了通过共享内存通信的方式。
❓如何去使用Channel进行共享数据呢?
1.3 Channel
Channel在Go语言中是一种特殊的数据类型,channel类似于队列总是遵循FIFO原则,保证数据收发顺序。
创建一个channel,
//channel是引用类型,创建后容量不会动态变化
//创建一个可以存放3个int类型的channel
c := make(chan int,3)
如何使用channel呢?
func main() {
c := make(chan int,3)
//发送数据到channel
go func() {
c <- 0
//关闭channel,只能由发送方关闭;关闭后不能发送数据,但可以读取channel中数据
close(c)
}
//接收channel的数据
go func() {
data,ok := <- c
//退出channel
exit <- true
close(exit)
}
}
1.4 Lock
Go中锁机制实现方法来自sync包,sync包内实现了两个重要的锁类型:
- 互斥锁,sync.Mutex;其实现借助于CAS+自旋+信号量
- 读写锁,sync.RWMutex;读锁RLock(),写锁Lock()
互斥锁如何编写?
//变量x,互斥锁
var (
x int
lock sync.Mutex
)
//加锁
func add() {
lock.lock()
x += 1
lock.Unlock()
}
//模拟并行调用互斥方法
func main() {
for i:= 0; i < 100; i++ {
go add()
}
}
1.5 WaitGroup
WaitGroup用途是使得主线程阻塞等待所有WaitGroup中的子协程结束。
WaitGroup代码编写
func main() {
var wg sync.WaitGroup
//添加计数
wg.Add(1)
go func() {
//延迟,减掉计数
defer wg.Done()
}
//阻塞直到计数为零
wg.Wait()
}
2 依赖管理
🤔 go依赖管理是如何实现的?会是Maven吗?
Go依赖管理的演进过程 GOPATH -> Go Vendor -> Go Module
- GOPATH无法实现package多版本控制
- 项目目录下添加vendor文件夹,所有依赖副本都放置于vendor文件夹下;解决了同一个package多版本冲突问题
- Go Module:通过go.mod文件管理依赖版本;定义版本规则和管理项目依赖关系
3 测试
🤔 Go语言如何进行测试的呢?
Go语言集成了测试功能,使用go test 命令。
| 类型 | 格式 | 作用 |
|---|---|---|
| 单元测试 | 方法名前缀为Test | 测试单元是否都符合预期 |
| 基准测试 | 方法名前缀为Benchmark | 测试方法执行性能 |
编写单元测试测试代码仅需遵守以下规则:
- 文件名必须以XX_test.go命名
- 方法必须是TestfuncName形式
- 使用 go test执行单元测试
Mock测试
在复杂项目中单元测试的测试结果会由于外部依赖的稳定性和幂等性导致测试结果的不稳定不准确,为此使用Mock接口进行Mock测试确保测试结果稳定性准确性。