Go 中 开启协程 非常简单,在函数前面增加一个 go
关键字就可以为一个函数开启一个协程。
CSP(Communicating Sequential Process)
Go 中提倡通过 通信共享内存 而不是通过共享内存而实现通信
那么如何通信呢,通过 channel
语法: make(chan 元素类型, [缓冲大小])
以下是一个例子:
0~9
到 src
中src
中每个数的平方发送到 dest
中dest
中每个数package main
func CalSquare() {
src := make(chan int) // 生产者
dest := make(chan int, 3) // 消费者 带缓冲解决生产者太快的问题
go func() { // 该线程发送0~9至src中
defer close(src) // defer 表示延迟到函数结束时执行 用于释放已分配的资源。
for i := 0; i < 10; i++ {
// <- 运算符 左侧为收集数据的一方 右侧为要传的数据
src <- i
}
}() // 立即执行
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
// 其他复杂操作
println(i)
}
}
func main() {
CalSquare()
}
可以看到每次都会是顺序输出,代表着Go是 并发安全的
Go 语言也保留了共享内存的做法,使用sync进行同步,如下
package main
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() { // x加到2000 使用锁则很安全
for i := 0; i < 2000; i++ {
lock.Lock() // 加锁
x += 3
x -= 2
lock.Unlock() // 解锁
}
}
func addWithoutLock() { // 不使用锁
for i := 0; i < 2000; i++ {
x += 3
x -= 2
}
}
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second) // 休眠 1s
println("WithoutLock x =", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second) // 休眠 1s
println("WithLock x =", x)
}
func main() {
Add()
}
ps:试了好多次都没冲突,乐。把运算稍微改复杂一点就有冲突了
任何大型项目开发都绕不开依赖管理,Go中的依赖主要经历了 GOPATH -> Go Vendor -> Go Module的演变 而现在主要采用Go Module的方式
go get
下载最新版本的包到src目录下这样的话,就会出现一个问题:无法实现多版本的控制(A、B依赖于同一个包的不同版本,寄)
vendor
文件,所有依赖包副本形式放在其中ps:感觉挺像前端的package.json……依赖问题真是绕不过去
这又产生了新的问题:
go.mod
文件管理依赖包版本go get/go mod
指令工具管理依赖包达成了终极目标:既能定义版本规则,又能管理项目依赖关系
可以类比一下Java中的Maven
go.mod
依赖标识语法:模块路径+版本来进行唯一标识
[Module Path][Version/Pseudo-version]
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
)
如上,需要注意的是:
+incompatible
格式为:${MAJOR}.${MINOR}.${PATCH}
V1.3.0、V2.3.0、 ……
MAJOR
版本表示是不兼容的API
MINOR
版本通常是新增函数或功能,向后兼容PATCH
版本一般是 修复 bug
格式为:${vx.0.0-yyyymmddhhmmss-abcdefgh1234}
yyyymmddhhmmss
),也就是提交 Commit
的时间abcdefgh1234
), 12 位的哈希前缀
commit
后 Go 都会默认生成一个伪版本号
如果X项目依赖了A、B两个项目,且A、B分别依赖了C项目的v1.3、v1.4两个版本,依赖图如上,最终编译时所使用的C项目的版本为如下哪个选项?(单选)
这个是Go进行版本选择的算法,选择最低的兼容版本,而1.4版本是向下兼容1.3的(语义化版本)。为什么不选1.3呢?他又不会向上兼容ovo,倘若还有1.5的话则不会选用1.5,因为1.4就是满足要求的最低兼容版本。
这些依赖去哪里下载呢?就是依赖分发
在github等代码托管系统中对应仓库上下载?
github是比较常见给的代码托管系统平台,而Go Modules
系统中定义的依赖,最终可以对应到多版本代码管理系统中某一项目的特定提交或版本
对于 go.mod
中定义的依赖,可以从对应仓库中下载指定软件依赖,从而完成依赖分发。
问题也有:
通过Proxy方式来解决以上问题
Go Proxy
是一个服务站点,它会缓存源站中的软件内容,缓存的软件版本不会改变,并且在源站软件删除之后依然可用
使用 Go Proxy 之后,构建时会直接从 Go Proxy 站点拉取依赖。
Go Modules通过 GOPROXY
环境变量控制如何使用 Go Proxy
服务站点URL列表,direct表示源站:GOPROXY="https://proxy1.cn, https://proxy2.cn,direct"
direct
表示源站。整体的依赖寻址路径,会优先从 proxy1
下载依赖,如果 proxy1
不存在,就下到 proxy2
寻找,如果proxy2
也不存在则会回源到源站直接下载依赖,缓存到 proxy
站点中。go get example.org/pkg
后缀 | 含义 |
---|---|
@update | 默认 |
@none | 删除依赖 |
@v1.1.2 | tag版本,语义版本 |
@23dfdd5 | 特定的commit |
master | 分支的最新commit |
go mod
后缀 | 含义 |
---|---|
init | 初始化,创建go.mod文件 |
download | 下载模炔到本地缓存 |
tidy | 增加需要的依赖,删除不需要的依赖 |
go mod tidy 可以在每次提交代码前执行一下,就可以减少构建整个项目的时间 |
测试一般分为回归测试、集成测试、单元测试,从前到后覆盖率逐层变大,成本却逐层降低,所以单元测试的覆盖率一定程度上决定这代码的质量。
单元测试主要包括:输入、测试单元、输出以及校对
单元的概念较广,包括接口,函数,模块等,用最后的校对来保证代码的功能与我们的预期相符
单元测试有以下几点好处
Go中的单元测试有以下规则:
_test.go
结尾func TestXxx(testing.T)
TestMain
函数中(测试前的数据装载配置、测试后的释放资源等)例子:
main.go
package main
func HelloTom() string {
return "Jerry"
}
main_test.go
package main
import "testing"
func TestHelloTom(t *testing.T) {
output := HelloTom()
expectOutput := "Tom"
if output != expectOutput {
t.Errorf("Expect %s do not match actual %s", expectOutput, output)
}
}
在实际项目中,单测覆盖率
单测需要保证稳定性和幂等性
而要实现这一目的就要用到mock
机制。
bouk/monkey: Monkey patching in Go
monkey是一个开源的mock测试库,可以对method,或者实例的方法进行mock,反射,指针赋值Mockey Patch 的作用域在 Runtime,在运行时通过 Go 的 unsafe 包,能够将内存中函数A的地址替换为运行时函数B的地址,将待打桩函数的实现跳转。
Go 语言还提供了基准测试框架
而我们在实际项目开发中,经常会遇到代码性能瓶颈问题,为了定位问题经常要对代码做性能分析,这就用到了基准测试。使用方法类似于单元测试
提到了
fastrand
,地址: bytedance/gopkg: Universal Utilities for Go
本节课主要讲了Go中的并发管理、依赖配置和测试,内容较多,需要好好消化。后面还有个项目实践环节,等明天在进行一个实践。
本节课内容来源于第三届青训营赵征老师的课程