Go语言进阶学习笔记 | 青训营

110 阅读2分钟

Go小白,自己学习使用

一、并发编程

CSP模型

在通信双方抽象出中间层,数据的流转由中间层来控制,通信双方只负责数据的发送和接收,从而实现了数据的共享

3f1780a3794d4c6aba688fc4867d6924~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp

Channel

在协程之间传递数据(协程:用户态,轻量级线程,KB)

主要特征有:

  1. 顺序进程:每个进程内部按顺序执行。
  2. 通信进程:进程间通过通信(Message Passing)来协作。
  3. 数据流:程序通过在进程间传递数据来工作

CSP实现主要通过goroutine和channel

  1. goroutine作为顺序执行的进程
go func(){
 //并发执行函数
}()
  1. channel用于goroutine间的通信
  • 发送操作 ch <- x
  • 接收操作 x := <- ch
  • 关闭操作 close(ch)

无缓冲通道

ch := make(chan int)

有缓冲通道

ch := make(chan int,2) //第二元素是缓冲大小

d1ce7874c9214948a045b09036b97519~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp 类似于生产者消费者模型,当缓冲区有东西或者有空余时,才可以进行存取。

例子:

var frequencies map[int]int //共享内存

go func() {
  //统计数词频次
  for {
    //读写frequencies map
  } 
}()

go func() {
  //记录最高频数
  for {  
    //读取frequencies map
  }
}()

两个goroutine直接读写同一份内存frequencies map,这是共享内存的方式,可能会出现资源竞争问题。

使用CSP通信的方式:

ch := make(chan map[int]int) //通信channel

go func() {
  //统计频次
  frequencies := make(map[int]int)
  
  //通过channel发送frequencies
  ch <- frequencies
}() 

go func() {
  //记录最高频数
  
  //从channel接收frequencies
  frequencies := <- ch
}()

两个goroutine之间通过channel传递frequencies,相当于共享了数据,实际上是复制内容,而不是直接共享内存。

并发安全 LOCK

为了处理同步访问共享资源,防止数据竞争,在Go中sync包提供了锁机制。

Go提供了两种线程安全的锁机制:

  1. sync.Mutex

互斥锁,确保同时只有一个goroutine可以访问共享数据。

var mu sync.Mutex
mu.Lock()
// 访问共享资源
mu.Unlock() 
  1. sync.RWMutex

读写互斥锁,可以同时允许多个读,但写时独占。

var mu sync.RWMutex
mu.RLock()
// 读共享资源
mu.RUnlock()

mu.Lock()
// 写共享资源
mu.Unlock()

WaitGroup

用于同步多个协程的执行。适用于需要并发执行多个任务并等待它们全部完成后才能继续执行后续操作的场景。

var wg sync.WaitGroup

func doWork(wg sync.WaitGroup) {

}

Add (delta int) //计数器+delta
Done() //计数器 -1
Wait() //阻塞直到计数器为0

二、依赖管理

  1. GOPATH无法实现package的多版本控制。

8d710768bbcc4a299cb7b64353a8e001~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp

在GOPATH下,所有的package都是安装到一个全局的位置,这意味着,对于一个package,只能有一个版本存在。但是在实际项目中,往往需要依赖不同版本的package,比如v1和v2。GOPATH无法处理这种情况,会导致冲突。如果安装v2,v1的代码就会被覆盖掉。

  1. Go Vendor

依赖寻址方式:vendor => GOPATH

65e1fe538b18458b8ed7ce22d50e9bbc~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp

3.Go Module

通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包。通过go mod init创建module,然后go get添加依赖,可以很便捷地使用modules。

当使用go命令获取、编译或运行依赖的模块时,Go会通过GOPROXY指定的代理服务器来下载模块,而不是直接从源码仓库下载。这样可以加速模块的获取,也可以避免一些网络问题或源码仓库的限制。

三、测试

3d01261e6ef4404b8ca9eaa46b1de746~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.webp 从上到下,覆盖率逐层变大,成本逐层降低。

  1. 单元测试
func Add(a int, b int) int{
  return a+b
}

func TestAdd(t *testing.T) {
  output:=Add(1, 2)
  expectOutput:=3
  if output != expectOutput{
    t.Error("Add failed")
  }
}

//Go语言内置的testing包提供了assert包来进行单元测试的断言
func Add(a int, b int) int {
   return a + b
}

func TestAdd(t *testing.T) {
   output := Add(1, 2)
   expectOutput := 3
   assert.Equal(t, expectOutput, output)
}
  • 当一个程序有多个分支时,就会产生多种测试案例,如果所有的测试案例都符合结果,那覆盖率就是100%。

  • 运行 go test xxx.go --cover,可以查看测试的覆盖率。

  • 一般覆盖率在50%~60%,较高覆盖率在80%+

  • 测试分支相互独立,全面覆盖

  1. Mock测试

为函数打桩(function stubbing)指对一个函数进行模拟替换。打桩主要目的是隔离被测代码,注入假数据来验证代码逻辑,而非真实环境。

  1. 基准测试
func BenchmarkFoo(b *testing.B) {
  for i := 0; i < b.N; i++ {
    // 测试代码 
  }
}