这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天.
一.GOROUTINE
1.并行与并发
- 并发:多线程程序在一个核的cpu上运行.
- 并行:多线程程序在多个核的cpu上运行.
- 一半的并发指广义上的并发,实际中,并行可以理解为实现并发的一个手段.
2.协程goroutine
1.协程:用户态,轻量级线程,栈KB级别
2.线程:内核态,线程跑多个协程,栈MB级别
3.CSP(Communicating Sequential Processes)
gorountine提倡通过通信共享内存而不是通过共享内存而实现通信.
go也保留通过共享内存实现通信的机制.
4.Channel
make(chan 元素类型,[缓冲大小])
func main() {
ch := make(chan string, 6)
go func(ch chan string) {
ch <- fmt.Sprintf("早上好")
}(ch)
fmt.Println(<-ch)
}
1.无缓冲通道: 会使发送的goroutine和接收的goroutine同步化. 2.有缓冲通道:通道的缓冲大小,代表通道的容量,如果容量满时不使用通道,那么goroutine会阻塞起来.
func producer(header string, channel chan<- string) {
for {
fmt.Println("ing...")
channel <- fmt.Sprintf("%s: %v", header, rand.Int31())
}
}
func customer(channel <-chan string) {
for {
//不使用chan,让chan的缓冲占满
}
}
func main() {
// 创建一个字符串类型的通道
channel := make(chan string, 2)
// 创建producer()函数的并发goroutine
go producer("producer", channel)
// 数据消费函数
customer(channel)
defer close(channel)
}
输出两个ing...后程序阻塞住了.
5.并发安全Lock
1.sync.Mutex
var (
x int64
lock sync.Mutex //互斥锁
lock1 sync.RWMutex //读写锁
)
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 addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
defer lock.Unlock()
x++
}
}
func addWithOutLock() {
for i := 0; i < 2000; i++ {
x++
}
}
sync.Mutex是互斥锁,是对传统并发程序对共享资源进行控制访问的主要手段.这是go对于共享内存实现通信的机制.
sync.Mutex只要声明就可以使用.对于不使用通道的goroutine,不加锁的结果是不确定的,这是危险的.其次,- 同级别互斥锁不能嵌套使用,这是很值得注意的.除此外还有sync.RWMutex读写锁等.
2.sync.WaitGroup计数器
add(n)操作会使得计数器+n,done()会使得计数器-1,wait()表示等待计数器归0.
func addWithLock() {
for i := 0; i < 2000; i++ {
lock2.Add(1)
x++
lock2.Done()
}
}
for i := 0; i < 5; i++ {
go addWithOutLock()
}
lock2.Wait()
使用sync.WaitGroup避免使用time.Sleep(),使用time.Sleep()其实并不稳定,你不能保证goroutine一定会在某个时间内完成.
二.依赖管理
1.go依赖管理演进
GOPATH->Go Vendor->Go Module
2.GOPATH
环境变量$GOPATH
2.1 结构
包括bin,pkg,src
bin是项目编译的二进制文件
pkg是项目编译的中间产物,加速编译
src是项目源代码
2.2 项目代码依赖src下的代码
go get下载最新版本的包到src目录下
2.3 弊端
1.无法实现package的多版本控制
比如: a和b依赖于某一个package的不同版本,a和b就不能同时构建成功.
3.Go Vendor
3.1 结构
1.比GOPATH增加了vendor文件,所有依赖包副本形式放在vendor内.
2.依赖寻址方式: vendor => GOPATH
3.2 好处
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突.
3.3 缺点
1.无法控制依赖的版本
2.更新项目有可能出现依赖冲突,导致编译出错.
比如:
项目a依赖于项目b和c,项目b依赖于项目D的v1版本,项目c依赖于项目D的v2版本.
4.Go Module
1.变化
1.通过go.mod文件管理里依赖包版本.
2.通过go get/go mod指令根据管理依赖包.
2,依赖三要素
1.配置文件,描述依赖 go.mod
2.中心仓库管理依赖 Proxy
3.本地工具 go get/mod
3.go.mod配置
依赖标识:[Module Path][Version/Pseudo-version]
4.依赖配置-version
1.语义化版本
{MINOR}.${PATCH}
如:v1.3.0
2.基于commit伪版本
vX.0.0-yyyymmddhhmmss-abcdefgh1234
如:v1.0.0-20201130134442-10cb98267c6c
5.依赖配置-indirect
A->B->C => A->B 直接依赖 A->C 间接依赖
间接依赖会用//indirect指出
6.依赖配置-incompatible
1.主版本2+模块会在模块路径增加/vN后缀
2.对于没有go.mod文件并且主版本2+的依赖,会+incompatible
7.依赖版本
go在对于两个不同版本的项目,是使用最低的兼容版本.
5.依赖分发
1.依赖分发-回源
直接使用版本管理依赖仓库的问题:
1.无法保证构建的稳定性:增加/修改/删除软件版本
2.无法保证依赖可用性:删除软件
3.增加第三方压力:代码托管平台负载问题
2.依赖分发-Proxy
Proxy会缓存原仓库的软件内容.
3.依赖分发-变量 GOPROXY
GOPROXY="proxy1.cn ,proxy2.cn ,direct"
服务站点URL列表,"direct"表示源站
依赖寻找模式:proxy1->proxy2->direct