这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。
一、Go语言进阶
1. 并发和并行:
并发是同一时间段内执行多个任务。并行是同一时刻执行多个任务。
从多线程程序运行的视角来看:并发是多线程程序在一个核的CPU上运行,并行是多线程程序在多个核的CPU上运行。
并行可以理解为实现并发的一个手段。
Go语言实现了并发性能极高的一个调度模型,通过高效的调度,可以最大限度的利用计算机资源,充分发挥多核计算机的优势。
2. Goroutine
协程:用户态,轻量级线程,栈KB级别。
线程:内核态,线程跑多个协程,栈MB级别。
go语言一次可以创建上万左右的协程。
操作系统的栈内存通常为2mb,go语言的goroutine一般为2kb。
- Go语言开启协程只需要在调用的函数前面加上一个关键字:go。
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
3. CSP (Communicating Sequential Processes)
Go语言提倡通过通信共享内存而不是通过共享内存而实现通信。
通过通信共享内存:Gorountine是go程序并发的一个执行体,通道相当于把协程做了一个连接 ,就像是一个传输队列,遵循先入先出,能保证收发数据的顺序。
通过channel(通道)可以让一个Gorountine发送特定的值到另一个Gorountine的通信机制。go保留了通过共享内存实现通信的机制。
通过共享内存实现通信: 必须通过一个互斥量对内存进行一个加锁,也就是需要获取临界区的一个权限。
这样,不同的Gorountine之间容易发生数据竞态的一个问题。一定程度上,影响程序的一个性能。
4. Channel
make(chan 元素类型,[缓冲大小])
- 无缓冲通道:make(chan int)
- 有缓冲通道:make(chan int, 2)
func CalSquare() {
src := make(chan int)
dest := make(chan int, 3)
go func() {
//子协程发送0~9数字
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)
}
}
5. 并发安全Lock
通过Lock加锁、Unlock解锁。
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)
}
7. WaitGroup
go语言可以通过使用WaitGroup实现并发任务的一个同步。
暴露了三个方法:wait(),Add(delta int),Done。内部就是维护了一个计数器。
计数器的值可以增加或者减少。增加一个任务,用add增加1,每个任务完成,用done方法为计数器减1。
最后调用wait方法阻塞,等待所有的并发任务执行完。计数器为0的时候,所有的并发任务都已经完成。
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
二、依赖管理
- 工程项目不可能基于标准库0~1编码搭建
- 管理依赖库
1. Go依赖管理演进
GOPATH —> Go Vendor -> Go module
GOPATH:存在弊端:项目A和项目B依赖于某一package的不同版本,无法实现package的多版本控制。
Go Vendor:项目目录下增加vendor文件,所有依赖包副本形式存放在项目vendor文件下。
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
弊端如下:无法控制依赖版本、更新项目又可能出现依赖冲突,导致编译出错。
Go Moudle:通过go.mod文件管理依赖版本。通过go get/go mod指令工具管理依赖版本。
2. 依赖管理三要素
配置文件,描述依赖:go mod。
中心仓库管理依赖:proxy。
本地工具:go get/mod
三、总结
通过本次课程,我了解到了Go语言并发相关的知识,并且明白了Go语言高性能的原理。其次了解了Go语言依赖管理的演进路线。在本次课程中,Go语言的协程的概念给我留下了深刻的印象,它是一种更加轻量级的线程,并且执行异步方法也更加的简单。