Go语言快速入手 - 工程进阶 | 青训营笔记

80 阅读2分钟

这是我参与「第五届青训营 」笔记创作活动的第2天

一、本堂课重点内容:

讲述了Go并发编程、工程依赖控制、项目从设计到编码的一个案例

二、详细知识点介绍:

1. Go并发

从并发编程的视角带大家了解 Go 高性能的本质

1.1. 并发 VS 并行

并发:多线程程序在一个核的CPU上运行

并行:多线程程序在多个核的CPU上运行

Go可以充分发挥多核优势,高效运行

协程:用户态,轻量级线程,栈MB级别

线程:内核态,线程跑多个协程,栈KB级别

package main

import (
   "fmt"
   "time"
)

func hello(i int) {
   println("hello goroutine : " + fmt.Sprintf("%d", i))
}

func HelloGoRoutine() {
   for i := 0; i < 5; i++ {
      go func(j int) {
         hello(j)
      }(i)
   }
   time.Sleep(time.Second)
}

func main() {
   HelloGoRoutine()
}

image.png

1.2 CSP

image.png

提倡通过通信共享内存而不是通过共享内存而实现通信

1.3 Channel

make(chan 元素类型, [缓冲大小])

image.png

package main

func CalSquare() {
   src := make(chan int)
   dest := make(chan int, 3)
  
   // A 协程发送 0-9的数字
   go func() {
      defer close(src)
      for i := 0; i < 10; i++ {
         src <- i
      }
   }()
   // B 子协程计算输入数字的平方
   go func() {
      defer close(dest)
      for i := range src {
         dest <- i * i
      }
   }()
   
   // 主协程输出最后的平方数
   for i := range dest {
      println(i)
   }
}

image.png

1.4 并发安全 Lock

package main

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 < 5; i++ {
      go addWithoutLock()
   }
   time.Sleep(time.Second)
   println("Without Lock: ", x)
   x = 0
   for i := 0; i < 5; i++ {
      go addWithoutLock()
   }
   time.Sleep(time.Second)
   println("With Lock: ", x)
}


func main() {
   Add()
}

image.png

1.5 WaitGroup

计数器:开启协程 + 1;执行结束 -1;

主线程阻塞知道计数器为 0

package main

import "sync"

func hello(i int) {
   println(i)
}
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()
}

func main() {
   ManyGoWait()
}

image.png

2. 工程依赖管理

了解 Go 语言依赖管理机制

历史:GOPATH -> GO Vendor -> Go Module

不同环境(项目)依赖的版本不同 控制依赖库的版本

GOPATH:无法实现package的多版本控制 GO Vendor:无法控制依赖的版本,更新项目又可能出现冲突

GoModule:

  • 通过 go.mod 文件管理依赖包版本
  • 通过 go get/go mod 指令工具管理依赖包

终极目标:定义版本规则和管理项目依赖关系

依赖配置:MAJOR.{MAJOR}.{MINOR}.${PATCH}

indirect:直接引用

incompatible:可能会存在一些不兼容的代码逻辑

依赖分发:Proxy

go get ...

@update 默认、@none删除、@v1.1.2 tag版本、@23dfdd5 特定的commit @mater 特定的分支

go mod init 初始化 传教go.mod

go mod download 下载模块到本地缓存

go mod tidy 增加所需要的依赖,删除不需要的依赖

3. 单元测试

从单元测试实践出发,加强大家的质量意识

回归测试 -> 集成测试 -> 单元测试 从上到下,覆盖率逐层变大,成本逐层降低

要求:

  1. 所有测试文件以 _test.go结尾

  2. func TestXXX(*testing.T)

  3. 初始化逻辑放在 TestMain 中

assert 断言:

import (
 "github.com/stretchr/testify/assert"
)

assert.Equal(t, expectOutput, output)

一般覆盖率:50%-60%,较高覆盖率80%+

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

测试单元粒度足够小,函数单一职责

image.png

外部依赖 => 稳定 & 幂等

还有Mock测试、基准测试

4. 项目实战

通过项目需求、项目拆解、结构设计、代码实现带领大家感受下真实的项目开发

需求描述 -> 需求用例 -> 分层结构 -> ER图 -> 组件工具(技术选型)-> DAO层 -> Service层(参数校验、准备数据、组转实体 ) -> Controller 层(构建View对象、业务错误码)-> Router路由(初始化数据索引、初始化引擎配置、构建路由、启动服务)-> 运行