语言进阶——Go并发编程
并发 VS 并行
- 并发:多线程程序在一个核的CPU上运行
- 并行:多线程程序在多个核的CPU上运行
- Go 可以充分发挥多核优势,高效运行
协程 VS 线程
- 协程:用户态,轻量级线程,栈KB级别
- 线程:内核态,线程跑多个协程,栈MB级别
实例 Goroutine
快速打印 hello goroutine:0~hello goroutine:4 原理:通过并行输出
func hello(i int){
Println("hello goroutine : "+fmt.Sprint(i))
}
func HelloGoRoutine(){
for i:=0;i<5;i++ {//创建5个线程
go func(j int){
hello(j)
}(i)
}
time.Sleep(time.Second)
}
运行结果:
CSP(Communicating Sequential Processes)
提倡通过通信共享内存而不是通过共享内存而实现通信
通道(Channel)的具体操作
make(chan 元素类型, [缓冲大小]) 典型的生产消费模型。
- 无缓冲通道:make(chan int)
- 有缓冲通道:make(chan int,2) (相当于货架)
func CalSquare(){
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)
}
并发安全Lock
- lock.lock()
- lock.unlock()
WaitGroup
计数器:开启协程+1;执行结束-1;主协程阻塞直到计数器为0.
- wq.Add()
- defer wg.DOne()
- wg.wait()
依赖管理
Go的依赖管理主要经历了三个阶段:GOPATH、Go Vendor、Go Module。
GOPATH
- GOPATH是在系统设的环境变量,是Go项目的工作区,目录下的bin是项目编译的二进制文件,pkg是项目编译的中间产物,src是项目源码。项目代码直接依赖src下的代码,go get下载的包到src目录下。
- 弊端:A和B依赖于某一package的不同版本,无法实现package的多版本控制。
Go Vendor
- 依赖寻址方式:Vendor=>GOPATH
- 通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
- 弊端:无法控制依赖的版本。更新项目后又可能出现依赖冲突而导致编译出错。
Go Module
- 通过 go.mod 文件管理依赖包版本
- 通过 go get/go mod 指令工具管理依赖包
- 终极目标:定义版本规则和管理项目依赖关系
依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
依赖配置——version
语义化版本、基于commit伪版本。
依赖配置——indirect
- A->B->C:A->B是直接依赖,A->是间接依赖。
依赖配置——incompatible
对于没有go.mod文件并且主版本2+的依赖,会+incompatible。 go在选择编译版本的时候会选择最低的兼容版本。(v1.3和v1.4选择v1.4)
依赖分发——变量GOPROXY
服务站点URL列表,“direct”表示源站
工具——go get
go get example.org / pkg +
- @update 默认
- @none 删除依赖
- @v1.1.2 tag版本,语义版本
- @23dfdd5 特定的commit
- @master 分支的最新commit
工具——go mod
go mod+
- init 初始化,创建go.mod文件
- tidy 增加需要的依赖,删除不需要的依赖
- download 下载模块到本地缓存
测试
- 回归测试
- 集成测试
- 单元测试
- 从上到下,覆盖率逐层变大,成本却逐层降低
单元测试
- 所有测试文件都以_test.go结尾
- 方法命名是func Testxxx(*testing.T)
- 初始化逻辑放到TestMain中
Mock测试
快速Mock函数:
- 为一个函数打桩
- 为一个方法打桩
基准测试
- 优化代码
- 内置测试框架已提供基准测试
项目实战
实现本地web服务。