1.语言进阶
1.1 并发编程
并发与并行的区别:
- 并发是指多线程程序在一个核的CPU上运行,主要通过时间片的切换来实现同时运行的状态。
- 并行是指多线程程序在多个核的cpu上运行,直接利用多核实现多线程的运行。Go可以充分发挥多核计算机优势,高效运行。
1.1.1 Goroutine
- 协程:用户态,轻量级线程,栈KB级别
- 线程:内核态,线程跑多个协程,栈MB级别
- GO语言一次可以创建上万协程。
package main
import (
"fmt"
"time"
)
func main() {
HelloRoutine()
}
func hello(i int) {
println("hello, goroutine :" + fmt.Sprint(i))
}
func HelloRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
1.1.2 CSP (Communicating Sequential Processes)
协程间的通信:Go提倡通过通信共享内存,而不是通过共享内存而实现通信。
通过通信共享内存涉及到通道(channel)的概念,通道就像是一个传输队列,遵循先入先出,能保证收发数据的顺序,channel让一个gorountine发送特定的值到另一个gorountine的通讯机制。go也保留着通过共享内存实现通信的机制,必须通过互斥量对内存加锁,获取临界区的权限,一定程度上影响性能。
1.1.3 Channel
make(chan 元素类型,[缓冲区大小])
- 无缓冲通道 make(chan int) 同步通道
- 有缓冲通道 make(chan int, 2)
下面的代码展示了channel的使用
A 子协程发送0~9数字
B 子协程计算输入数字的平方
主协程输出最后的平方数
package main
func main() {
CalSquare()
}
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() { //A 生产
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
go func() { //B 消费
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest { //主协程
println(i)
}
}
1.1.4 并发安全Lock
下面的例子对变量执行2000次+1操作,5个协程并发执行。不加锁会输出未知的结果,这就是并发安全问题。通过对临界区的控制来保证并发安全。
package main
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock", x)
}
func main() {
Add()
}
1.1.5 WaitGroup
- Add(delta int) 计数器+delta
- Done() 计数器-1
- Wait() 阻塞直到计数器为0
- 计数器 开启协程+1;执行结束-1;主协程阻塞直到计数器为0。
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
}(i)
}
wg.Wait()
}
2. 依赖管理
背景:
- 工程项目不可能基于标准库0~1编码搭建
- 管理依赖库
2.1 Go依赖管理演进
GOPATH--->Go Vendor--->Go Module 版本迭代的目的:
- 不同环境(项目)依赖的版本不同
- 控制依赖库的版本
2.1.1 GOPATH
- 环境变量 $GOPATH Go项目的工作区
- 项目代码直接依赖src下的代码
- go get下载最新版本的包到src目录下
GOPATH-弊端
- 场景:A和B依赖于某一package的不同版本
- 问题:无法实现package的多版本控制
2.1.2 Go Vendor
- 项目目录下增加vendor文件,所有依赖包副本形式放在 $ProjectRoot/vendor
- 项目寻址方式:vendor=>GOPATH
- 通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题
Go Vendor-弊端
- 无法控制依赖的版本
- 更新项目又可能出现依赖冲突 ,导致编译出错
2.1.3 Go Module
- 通过 go.mod文件管理依赖包版本
- 通过 go get/go mod指令工具管理依赖包
- 终极目标:定义版本规则和管理项目依赖关系
2.2 依赖管理三要素
- 配置文件,描述依赖 go.mod
- 中心仓库管理依赖库 Proxy
- 本地工具 go get/mod
2.2.1 依赖配置-go.mod
依赖标识:[Module Path][Version/Pseudo-version]
2.2.2 依赖配置-version
语义化版本
${MAJOR}.${MINOR}.${PATCH} 不同MAJOR是不兼容的,MINOR在MAJOR下兼容 PATCH补丁
V1.3.0
V2.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后缀。
- 对干没有ao.mod 文件并且主版本2+的依赖,会+incompatible
2.2.5 依赖分发-回源
- 无法保证构建稳定性增加/修改/删除软件版本
- 无法保证依赖可用性删除软件
- 增加第三方压力,代码托管平台负载问题