这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
一、Go语言的并发编程
go语言引进了协程(Goruntine)的概念,它是用户级线程。
用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被剥夺,cpu资源可以被其他程序获取。
内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序
线程和协程的区别:
线程:用户态,轻量级线程,栈KB级别
协程:内核态,一个线程能跑多个协程,栈MB级别
并行和并发的区别:
并发:cpu在多个线程中来回切换,不能达到同时执行。
并行:多核cpu分别执行多个线程,可以达到同时执行的效果。
协程可以通过通信来实现共享内存,当然也可以通过共享内存来实现通信与c/c++类似,但是协程更加提倡第一种。通过通信来实现共享内存是怎么来实现的呢?就要抛出通道channel的概念,channel是两个协程之间的桥梁,他和slice和map一样,必须make才能使用,而若make时容量为0则两个管道必须同步才能通信,若容量不为0,相当于加了缓冲,channel变成了阻塞队列,channel可以指定方向和类型,当然不指定方向默认为双向传输。
下面是一个实现协程之间双向传输的管道的例子(消费者和生产者模型):func CalSquare() {
//有缓冲区的通道
dest := make(chan int, 3)
//无缓冲区的通过 协程同步
src := make(chan int)
go func() {
//做最后的收尾工作
defer func() {
fmt.Println("关闭了src")
close(src)
}()
for i := 0; i < 9; i++ {
//往src管道发送信息
src <- i
}
}()
go func() {
defer func() {
fmt.Println("关闭了dest")
close(dest)
}()
for i := range src {
//向dest管道输送
//这是一个有缓冲区的管道 缓冲区满 会协程阻塞 直到消费者取完
dest <- i * i
}
}()
for i := range dest {
fmt.Println(i)
}
}
至于协程也有通过锁还有计数器等机制来保证安全并发。计数器的用法给我的感觉类似于c中信号量机制,计数器是为了防止子协程还没结束,主协程就结束了的情况。
var wg sync.WaitGroup
//计数器初始化为5
wg.Add(5)
for i := 0; i < 5; i++ {
//开启了一个协程
go func(j int) {
//每个协程结束后计数器剪一
defer wg.Done()
hello(j)
}(i)
}
//直到计数器为0接触阻塞
wg.Wait()
总而言之,Goroutine就是一个是轻量且能实现高并发的手段。
二、包的依赖管理
gomod是当前go语言包管理的主基调,当期的主流就是这个。它相比于之前的管理方式的优点就是定义了版本规则和管理项目的依赖关系。它的方式是:
1.通过go.mod文件管理依赖包版本
2.go get/go mod 指令工具管理依赖包
依赖管理的三要素:1.配置文件描述依赖 go.mod
2.中心仓库管理依赖库 Proxy
3.本地工具 go get/mod
一个实例:module Goroutine //依赖管理基本单元
go 1.19 //原生库
require (
//indirect 是间接依赖的意思
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
依赖配置-version
1.语义化版本
2.基于commit伪版本
使用go get获取原仓库包,由于所需依赖来自不同的仓库,所以需要一个代理点帮我们分发依赖,在goproxy设置我们的代理即可(稳定可靠)。
go get:
go mod: