这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
go语言进阶
1. 并发、并行
并行:指在同一时刻,有多条指令在多个处理器上同时执行。就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。所以无论从微观还是从宏观来看,二者都是一起执行的。
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。
goroutine: go的协程,内核态,一个线程里面可以有多个协程,线程栈一般在MB级别,协程栈一般在KB级别。
Example:
import (
"fmt"
"sync"
)
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func ManyGo() {
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()
}
/*
output:
hello world : 4
hello world : 3
hello world : 0
hello world : 1
hello world : 2
*/
2. CSP(communicating Sequential Processes)
go语言提倡通过通信共享内存而不是通过共享内存来实现通信
3. Channel
- Go语言中的通道(channel)是一种特殊的类型。在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。goroutine 间通过通道就可以通信。
- 声明方法
- 无缓冲通道 make(chan int)
- 有缓冲通道 make(chan int, 2)
4. 并发安全Lock
sync.Mutex:
- 一个互斥锁可以被用来保护一个临界区或者一组相关临界区。我们可以通过它来保证,在同一时刻只有一个 goroutine 处于该临界区之内。
- 为了兑现这个保证,每当有 goroutine想进入临界区时,都需要先对它进行锁定,并且,每个 goroutine 离开临界区时,都要及时地对它进行解锁。
- 锁定操作可以通过调用互斥锁的Lock方法实现,而解锁操作可以调用互斥锁的Unlock方法。
5. WaitGroup
Add(delta int):
- 主函数需要等待的delta个协程数 Done:
- 对计数器减一 Wait:
- 阻塞等待直到计数器为0才推出主函数
依赖管理
1. GOPATH
GOPATH 是Go语言中使用的一个环境变量,它使用绝对路径提供项目的工作目录。
.
|--bin //项目编译的二进制文件
|--pkg //项目编译的中间产物,加速编译
|--src //项目源码
- 项目代码直接依赖src下的代码
- go get可以下载最新版本的依赖包到src目录下 缺点:无法实现package的多版本控制
2. Go Vender
- 项目目录下增加vender文件,所有依赖包副本形式放在$ProjectRoot/vender
- 依赖寻址方式:vender -> GOPATH
.
|--readme.md
|--dao
|--handler
|--main.go
|--service
|--vender
通过每个项目引入一份依赖的副本,解决了多个项目需要一个package依赖的冲突问题。
缺点:
- 无法控制依赖的版本
- 更新项目又可能出现依赖冲突,导致编译出错
3. Go Module
- 通过go.mod文件来管理依赖包版本
- 通过go get/go mod 指令工具管理依赖包
单元测试
1、要开始一个单元测试,需要准备一个 go 源码文件,在命名文件时需要让文件必须以_test结尾。 2、单元测试源码文件可以由多个测试用例组成,每个测试用例函数需要以Test为前缀
func TestXXX( t *testing.T )
3、测试用例文件不会参与正常源码编译,不会被包含到可执行文件中。
4、测试用例文件使用 go test 指令来执行,没有也不需要 main() 作为函数入口。所有在以_test结尾的源码内以Test开头的函数会自动被执行。
5、测试用例可以不传入*testing.T参数
6、新建的测试文件一般和源文件 放同一目录
7、go单元测试,引入包 testing
Example:
//待测文件hello.go
package main
import "fmt"
func Hello() string {
return "hello world"
}
func main(){
fmt.Print(Hello())
}
//测试文件hello_test.go
package main
import "testing"
func TestHello(t *testing.T) {
a:= Hello()
if a != "hello world" {
t.Error("不通过")
}
}
//1、单个文件进入到测试文件所在目录
go test hello_test.go hello.go
ok command-line-arguments (cached)
//2、如果该目录有go.mod存在,直接 go test
>go test
PASS
ok hello 3.634s
小tips
- 一般覆盖率:50%-60%,较高覆盖率:80%+
- 测试分支相互独立,全面覆盖
- 测试单元粒度足够小,函数单一职责
单元测试-Mock
在比如对文件进行测试的情况下,如何文件被修改,之前的测试函数可能就失效了,就得重新编写测试函数,效率低下,此时就需要进行Mock测试。不需要依赖本地文件,随时都可以进行测试。
基准测试
Go 的基准测试文件名也必须以_test.go结尾。同时也必须导入testing包。基准测试函数必须以Bechmark开头,接收一个指向testing.B类型的指针作为唯一参数。为了让基准测试框架能准确测试性能,它必须在一段时间内反复运行这段代码,所以这里使用了for循环。 // BenchmarkSprintf 对 fmt.Sprintf 函数进行基准测试
func BenchmarkSprintf(b *testing.B) {
number := 10
b.ResetTimer()
for i := 0; i < b.N; i++ {
fmt.Sprintf("%d", number)
}
}