Go语言上手-工程实践 | 青训营笔记

75 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记

go语言进阶

1. 并发、并行

image.png image.png

并行:指在同一时刻,有多条指令在多个处理器上同时执行。就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。所以无论从微观还是从宏观来看,二者都是一起执行的。

并发(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)

image.png

go语言提倡通过通信共享内存而不是通过共享内存来实现通信

3. Channel

  • Go语言中的通道(channel)是一种特殊的类型。在任何时候,同时只能有一个 goroutine 访问通道进行发送和获取数据。goroutine 间通过通道就可以通信。
  • 声明方法
    • 无缓冲通道 make(chan int)
    • 有缓冲通道 make(chan int, 2)

image.png

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)
	}
}