这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
并发&&并行
优雅的并发编程范式,完善的并发支持,出色的并发性能是Go语言区别于其他语言的一大特色
- 多线程程序在单核心的 cpu 上运行,称为并发。
- 多线程程序在多核心的 cpu 上运行,称为并行。
并发
并发主要是指通过切换时间片来实现的伪同时运行,可以达到压榨cpu性能的目的,但是上下文切换是有成本的,不仅占内存,而且多个线程共享同一个资源时可能会发生死锁。
并行
并行和并发是有可能同时发生的,即混用,且并发和并行是操作系统根据cpu的实际占用情况决定的,并非人为决定,即一个宏观上的并发有可能是并发,也有可能是并行。而无论是并发还是并行都有利于都提高了程序对CPU资源的利用率,最大限度地利用CPU资源。
协程Coroutine
协程并不是Go语言特有的机制,像Lua、Ruby、Python、Kotlin、C/C++等也都有协程的支持,区别在于有的是从语言层面支持、有的通过插件类库支持,而Go语言是原生语言层面支持
协程通过在线程中实现调度,避免了陷入内核级别的上下文切换造成的性能损失,进而突破了线程在IO上的性能瓶颈,Go语言中,用关键字go开启多协程工作,代码如下:
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func ManyGo() {
var wg sync.WaitGroup //创建等待组
for i := 0; i < 5; i++ {
wg.Add(1) //等待组的计数器 +1
go func(j int) {
defer wg.Done() //等待组的计数器 -1
hello(j)
}(i) //函数匿名调用
}
wg.Wait() //当等待组计数器不等于 0 时阻塞直到变 0
}
通道Channel
func CalSquare() {
src := make(chan int) //创建通道src
dest := make(chan int, 3) //创建缓冲区容量为3的通道dest
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,sync.Mutex指定锁类型为互斥锁
var lock sync.Mutex
//方法同java中的lock一致
lock.Lock()
lock.Unlock()
对于互斥锁应该注意,如果一个协程已经拿到了锁,再次加锁时该锁已被占用,协程会自动挂起等待别的协程释放锁,但此锁还没有被该协程释放,因此会出现死锁的情况。
依赖管理
依赖管理为了解决依赖冲突应运而生
GOPATH
一开始,go直接使用GOPATH下的src存放依赖源码,这样所有项目的依赖都指向GOPATH,由于go没用版本管理的手段,导致不同项目对于某个依赖的版本不同产生冲突。
Go Vendor
后看,go通过引入Go Vendor,在项目目录下新建vendor文件夹用于存放依赖库文件副本,使得不同项目可以依赖不同的依赖库版本,但是,当发生次级依赖,也就是某个项目所依赖的多个项目依赖于某一个其他项目,此时依旧产生依赖冲突。
Go Module
最后,go采用了Go Module进行依赖管理并沿用至今,Go Module通过项目路径中的go.mod文件声明所需依赖的名称和版本范围,并通过go.sum文件记录项目实际使用的依赖和版本,在go中我们可以通过使用go.get和go.mod两个工具进行依赖的添加和移除。
通过本次课程的学习,我们了解到了go语言在处理问题时有着独到而又先进的思想,包括用户态的栈kb级的协程,以及类似目前主流却又更加方便地依赖管理