这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记。
Go语言上手-基础语言
标准库
Golang的os库提供了丰富且强大的功能,像我们所熟知的一些函数os.Open,同时标准库还定义了File、Process等类型,实现了对其操作的许多方法,极大的便利了用户对文件,进程等操作。
package main
import (
"fmt"
"os" // https://pkg.go.dev/os
"os/exec"
)
func main() {
fmt.Println(os.Args) // 返回执行 go run 参数 时其中包含的参数
fmt.Println(os.Setenv("Hello", "/tmp")) // 设置临时变量
fmt.Println(os.Getenv("Hello"))
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf))
}
tools
方便Golang做web开发的一些网站:
- JSON 转换为 Golang Struct oktools.net/json2go(同时包… JSON 格式转换、Base 解码、Hash、加密等)
- 将 curl 命令转换为 Golang 代码 curlconverter.com/#go
Go语言上手-工程实践
从并发视角理解Golang
一个经典面试问题:并发和并行是什么?
并发:多线程程序在单核 CPU 上运行,本质是通过划分时间切片,榨干 CPU 资源。
并发分为广义和狭义,上面是狭义的定义,广义上,可以说我完成了一个高并发项目。
并行:多线程程序在多个核的 CPU 上运行,本质是能够同一时间能够处理多个任务。
concurrency is not parallelism演讲:go.dev/blog/waza-t…
线程:内核态,轻量级线程,栈MB级别。
协程:用户态,线程跑多个协程,栈KB级别。
线程是CPU调度的基本单元,协程是CPU调度的基本单元。
A goroutine is a lightweight thread managed by the Go runtime.
goroutine vs coroutine(协程)
两者存在相似的地方,显然“goroutine”这个名字源于这种相似性。
协程和 goroutine 的区别在于:
- goroutines 意味着并行性,可以并行执行;协程一般不会,通常顺序执行
- goroutines 通过channel进行通信;协程通过 yield 和 resume 操作进行通信
coroutine 的运行机制属于协作式任务处理,早期的操作系统要求每一个应用必须遵守操作系统的任务处理规则,应用程序在不需要使用 CPU 时,会主动交出 CPU 使用权。如果开发者无意间或者故意让应用程序长时间占用 CPU,操作系统也无能为力,表现出来的效果就是计算机很容易失去响应或者死机。
goroutine 属于抢占式任务处理,已经和现有的多线程和多进程任务处理非常类似。应用程序对 CPU 的控制最终还需要由操作系统来管理,操作系统如果发现一个应用程序长时间大量地占用 CPU,那么用户有权终止这个任务。
对于大部分人来说,goroutine就是Go中的协程。本质上,goroutine是对线程的复用。
提倡通过通信共享内存而不是通过共享内存而实现通信。
Go语言更多问题解答:go.dev/doc/faq
Go的依赖管理
Go 依赖管理演进
GOPATH -> Go Vendor -> Go Module
GOPATH:
- 环境变量 $GOPATH
- 项目代码直接依赖 src 下面的代码
- go get 下载最新版本的包到 src 目录下
弊端:两个项目依赖某一 package 的不同版本时,无法做到 package 多版本并发控制
Go Vendor:
- 项目下增加 vendor 文件,所有依赖包副本形式放在 $ProjectRoot/vendor
- 依赖寻址方式:vendor => GOPATH
解决了GOPATH的弊端
弊端:一个项目依赖两个 package 同时两个包又依赖某一 package 的不同版本时,无法控制依赖版本,更新项目可能出现依赖冲突,导致编译错误
Go Module:
- 通过 go.mod 文件管理依赖包版本
- 通过 go get/ go mod 指令工具管理依赖包
依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
依赖配置
go.mod
module example/project/app // 依赖管理基本单元
go 1.16 // 原生库
// 单元依赖
require (
example/lib1 v1.0.2
example/lib2 v1.0.0 // indirect
example/lib3 v0.1.0-20190725025543-5a5fe074e612
example/lib4 v0.0.0-20180306012644-bacd9c7ef1dd // indirect
example/lib5/v3 v3.0.2
example/lib6 v3.2.0+incompatible
)
依赖标识:[Module Path] [Version/Pseudo-version]
version
语义化版本
${MAJOR}.${MINOR}.${PATCH} v1.3.0 v2.3.0
基于 commit 伪版本
vX.0.0-yyyymmddhhmmss-abcdefgh1234 v0.0.0-20220401081311-c38fb59326b7 v1.0.0-20201130134442-10cb98267c6c
indirect
A->B->C = A->B 直接依赖 / A->C 间接依赖
incompatible
- 主版本2+模块会在模块路径增加 /vN
- 对于没有 go.mod 文件并且主版本2+的依赖,会+incompatible
依赖分发
Go Proxy 是一个服务站点,它会缓存源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用,从而实现了供“immutability”和“available”的依赖分发;使用 Go Proxy 之后,构建时会直接从 Go Proxy 站点拉取依赖。
GOPROXY="proxy1.cn, proxy2.cn, direct"
服务站点URL列表,"direct"表示源站。 Proxy 1 -> Proxy 2 -> Direct
**工具 **
go get example.org/pkg
@update 默认
@none 删除依赖
@v1.1.2 tag版本,语义版本
@23dfdd5 特定的commit
@master 分支的最新commit
go mod
init 初始化,创建 go.mod 文件
download 下载模块到本地缓存
tidy 增加需要的依赖,删除不需要的依赖
测试
单元测试
规则:
- 所有测试文件以 _test.go 结尾
- func TestXxx(*testing.T)
- 初始化逻辑放到TestMain中
运行:go test [flags] [packages]
assert包:使用 assert 包,方便编写测试文件
tips:
- 一般覆盖率 50%~60%,较高覆盖率80%+
- 测试分支相互独立、全面覆盖
- 测试单元粒度足够小,函数单一职责
外部依赖->稳定&幂等
快速Mock函数:为一个函数打桩;为一个方法打桩
monkey:github.com/bouk/monkey
基准测试
使用rand随机选择模拟场景,进阶可使用fastrand提升测试效率。