这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
课内:Go 工程实践
从工程实践角度,讲授在企业项目实际开发过程中的所遇的难题,重点讲解 Go 语言的进阶之路,以及在其依赖管理管理过程中如何演进。
重点
- 语言进阶:从并发角度理解 Go 的高性能本质;
- 依赖管理:了解 Go 语言依赖管理的演进路线;
- 测试:单元实践;
- 项目实战:理解真实项目开发。
细节
语言进阶
并发vs并行
- 并发:多线程程序在cpu的一个核上运行;
- 并行:多线程程序在cpu的多个核上运行;
Go 可以充分发挥多核优势,高效运行。
Goroutine 协程
- 协程较轻量,线程可以并发运行多个协程
- 开启协程:快速打印,不考虑顺序
func Hello() {
for i := 0; i < 5;i++ {
go func(j int) {// 使用go关键字开启协程
fmt.Println(j)
}(i) //传入i
}
}
CSP
提倡通过通信共享内存,而不是共享内存实现通信。
Channel
- Channel是Go中的一个核心类型,可以把它看成一个管道,通过它并发核心单元就可以发送或者接收数据进行通讯(communication)。它的操作符是箭头
<-。
make (chan 元素类型,[缓冲大小])
make(chan int) // 无缓冲通道
make(chan int ,2) // 有缓冲通道
ch <- v // 发送值v到Channel ch中
v := <- ch // 从Channel ch中接收数据,并将数据赋值给v
- 它包括三种类型的定义。可选的
<-代表channel的方向。如果没有指定方向,那么Channel就是双向的,既可以接收数据,也可以发送数据。
chan T // 可以接收和发送类型为 T 的数据
chan<- float64 // 只可以用来发送 float64 类型的数据
<-chan int // 只可以用来接收 int 类型的数据
- 使用 defer,延迟关闭 channel 。
- Go语言的 defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
- 当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出),下面的代码是将一系列的数值打印语句按顺序延迟处理,如下所示
package main
import (
"fmt"
)
func main() {
fmt.Println("defer begin")
// 将defer放入延迟调用栈
defer fmt.Println(1)
defer fmt.Println(2)
// 最后一个放入, 位于栈顶, 最先调用
defer fmt.Println(3)
fmt.Println("defer end")
}
// 输出
defer begin
defer end
3
2
1
并发安全Lock
sync.Mutex是一个互斥锁,可以由不同的goroutine加锁和解锁。
- 共享内存时,需要使用 Lock,保证每个协程不会冲突使用内存。
sync.Mutex是 Go 标准库提供的一个互斥锁,当一个goroutine获得互斥锁权限后,其他请求锁的goroutine会阻塞在Lock()方法的调用上,直到调用Unlock()方法被释放。
var {
x int
lock sync.Mutex// 互斥锁
}
WaitGroup
sync.WaitGroup 使用等待组进行多个任务的同步,等待组可以保证在并发环境中完成指定数量的任务
- 在 sync.WaitGroup(等待组)类型中,每个 sync.WaitGroup 值在内部维护着一个计数,此计数的初始默认值为零。
- 等待组方法
| 方法名 | 功能 |
|---|---|
| Add(delta int) | 等待组的计数器 +int |
| Done() | 等待组的计数器 -1 |
| Wait() | 当等待组计数器不等于 0 时阻塞,直到变 0。 |
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
// 声明一个等待组
var wg sync.WaitGroup
// 准备一系列的网站地址
var urls = []string{
"http://www.github.com/",
"https://www.qiniu.com/",
"https://www.golangtc.com/",
}
// 遍历这些地址
for _, url := range urls {
// 每一个任务开始时, 将等待组增加1
wg.Add(1)
// 开启一个并发
go func(url string) {
// 使用defer, 表示函数完成时将等待组值减1
defer wg.Done()
// 使用http访问提供的地址
_, err := http.Get(url)
// 访问完成后, 打印地址和可能发生的错误
fmt.Println(url, err)
// 通过参数传递url地址
}(url)
}
// 等待所有的任务完成
wg.Wait()
fmt.Println("over")
}
依赖管理
GOPATH
- bin:项目编译的二进制文件
- pkg:项目编译的中间产物,加速编译
- src:项目源码
Go Vendor
- 在项目目录下增加 Vendor ,引入依赖副本
Go Module
- 通过 go.mod 文件管理依赖包版本
module test/test_1/main // 依赖管理基本单元
go 1.19
require ( // 单元依赖
// 依赖标识:[Module Path][Version/Pseudo-version]
)
依赖配置 - indirect
对于 a -> b -> c
- a -> b 为直接依赖
- a -> c 为间接依赖(indirect)
测试
回归测试 -> 集成测试 -> 单元测试, 覆盖面增加,成本下降
单元测试
-
提升效率,保证质量
-
规则:
所有测试文件以 _test.go 结尾,测试函数命名为 func TestXxx(*testing.T),初始化理解放到 TestMain
- 覆盖率
测试数 | 测试水准 | 高水平测试
- 依赖
外部依赖 => 稳定 & 幂等
- Mock
为一个函数打桩,为一个方法打桩。
A 替换 B,B为原函数,A为打桩函数。
基准测试
优化代码
实践
需求设计 | 代码开发 | 测试运行
需求设计
需求描述
需求用例
话题,帖子
ER 图
描述内容与关系
分层结构
数据层 -> 逻辑层 -> 视图层
- 数据层:数据 Model,外部数据的增删改查;
- 逻辑层:业务 Enity,处理核心业务逻辑输出;
- 视图层:视图 view,处理和外界的交互逻辑。
组件工具
总结
语言进阶部分用处挺大