工程实践角度
1.语言进阶--Go并发编程
1.基于Go并发编程相关概念
(引用自课程PPT)
Go语言开发快的原因 1.Go可以充分发挥多核优势,高效运行。Go语言就是为并发而生的。1.1Goroutine
协程与线程 在go语言中,协程被认为是轻量级的线程, 和线程不同的是,操作系统内核 感知不到协程的存在, 协程的管理依赖于Go语言运行时自身提供的调度器 同时Go语言中的协程是从属于某一个线程的。
线程处于内核态,栈MB级别,线程跑多个协程; 协程处于用户态,是轻量级别的线程,栈KB级别。
package main
import (
"fmt"
"time"
)
func hello(i int) {
println("hello goroutine:" + fmt.Sprint(i))
}
func main() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
//乱序输出说明是并行
//hello goroutine:4
//hello goroutine:3
//hello goroutine:2
//hello goroutine:1
//hello goroutine:0
1.2CSP协程之间的通信
Go提倡通过通信共享内存,而不是通过共享内存实现通信。
通过通信共享内存使用通道,通过内存实现通信使用互斥量,而互斥量一定程度上会影响性能,因此提倡通信共享内存。
1.3Channel
定义channnel的方法:make(chan 元素类型[缓冲大小])
//无缓冲通道
//可以实现发送的goroutine和接收的goroutine同步,也被称为同步通道
make(chan int)
//有缓冲通道
make(chan int ,2)
例子
package main
func main() {
src := make(chan int)
dest := make(chan int, 3) //定义两个管道
go func() {
defer close(src)
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)
}
} //0 1 4 9 6 25 36 49 64 81
1.4 并发安全Lock(sync包下关键字实现并发安全)
协程的执行顺序不确定会导致并发安全问题。多个协程执行同一个方法,且多个线程存在共享变量,由于这些协程执行顺序是不确定的,由这些协程同步修改使用一些变量时,产生了不确定的结果。
可以使用锁实现并发安全。
1.5 WaitGroup(sync包下关键字实现协程同步)
go中使用WaitGroup实现并发任务的同步。内部维护了一个计数器。
(引用自课程PPT)
``` package main import ( "fmt" "sync" ) func hello(i int) { println("hello goroutine:" + fmt.Sprint(i)) } func main() { 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() } //hello goroutine:4 //hello goroutine:2 //hello goroutine:3 //hello goroutine:1 //hello goroutine:0 ```2.依赖管理演进
实际项目中我们要将更多的精力去关注业务逻辑,而其他的依赖可以通过sdk的方式引入,因此对依赖包的管理就十分重要。
2.1 Go的依赖管理
GOPATH->Go Vender->Go Module
2.1.1 GOPATH
GOPATH是GO语言支持的环境变量,是GO项目的工作区。
问题:无法实现package的多版本控制
2.1.2Go Vender
项目目录下增加vender文件,所有依赖包副本形式放在vender目录下。依赖寻址方式:vender->GOPATH
问题:无法控制依赖的版本;更新项目又可能出现依赖冲突,导致编译错误
2.1.3 Go Module
通过 go.mod 文件管理依赖包版本; 通过 go get/go mod 指令工具管理依赖包
2.2 依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
与Java中的maven类似。
2.2.1依赖配置-go.mod
依赖中标识了版本号
2.2.2 依赖配置-version
//语义化版本
${SIMAJOR}.${SIMINOR}.${SPATCH}
${大版本}.${新增函数}.${代码修复}
//V1.3.0V2.3.0
//基于 commit 伪版本
vx.0.0-yyyymmddhhmmss-abcdefgh1234
//版本前缀-时间戳-哈希前缀
v0.0.0-20220401081311-c38fb59326b7
v1.0.0-20201130134442-10cb98267c6c
2.2.3 依赖配置- indirect
间接依赖标识
2.2.4 依赖配置- incompatible
- 主版本2+模块会在模块路径增加/vN
- 后缀对于没有go.mod文件并且主版本2+的依赖,会加incompatible(标识出来可能会出现一些不兼容的代码逻辑)
2.2.5 依赖配置-依赖图
选择构建的最低版本
2.2.6 依赖分发-变量 GOPROXY
Proxy能够保证依赖的稳定和可靠性。
2.2.7 工具-go get
go get example.org/pkg:
- @update 默认
- @none 删除依赖
- @v1.1.2 tag版本,语义版本
- @23dfdd5 特定的commit
- @master 分支的最新commit
- 2.2.8 工具-go mod
go mod
- init 初始化,创建go.mod文件
- download 下载模块到本地缓存
- tidy 增加需要的依赖,删除不需要的依赖
3.测试
测试分为:回归测试、集成测试、单元测试。覆盖率逐渐变大,成本逐渐减低。