青训营笔记---复习专辑(4)Go 语言进阶与依赖管理

40 阅读5分钟

1.Goroutine

image.png

协程:用户态,轻量级线程,栈KB级别。 线程:内核态,线程跑多个协程,栈MB级别。 线程上可以并发跑多个协程。

2."快速"打印hello goroutine:0~hello goroutine :4


import ("fmt"
"time"
)
func hello(i int){
  println("hello goroutine:"+fmt.Sprint(i))
}
//快速需要开启多个协程去打印
func HelloGoRoutine(){
for i:=0;i<5;i++{
  //开启协程的方法,在调用函数的时候在函数前面加一个go关键字
  go func(j int){
    hello(j)
  }(i)
}
time.Sleep(time.Second)
//为了保证子协程执行完之前主协程不退出
}
func main() {
    HelloGoRoutine()
}

开启协程的方法,在调用函数的时候在函数前面加一个go关键字 3.CSP-(Communiciating Sequential Processes)

GO语言提倡通信共享内存,而不是通过共享内存而实现通信。

image.png

4.Channel

goroutine 是 Go 程序并发执行的实体,channel (通道) 则是它们之间的连接,用于多个 goroutine 之间互相通信。通道可以让一个 goroutine 发送特定类型值到另一个 goroutine,每一个通道可以发送数据类型称为通道的 元素类型

make(chan 元素类型,[缓冲大小])

  1. 无缓冲通道 make(chan int)
  2. 有缓冲通道 make(chan int,2)

Go语言中有缓冲的通道(buffered channel)是一种在被接收前能存储一个或者多个值的通道。这种类型的通道并不强制要求 goroutine 之间必须同时完成发送和接收。通道会阻塞发送和接收动作的条件也会不同。只有在通道中没有要接收的值时,接收动作才会阻塞。只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞。

这导致有缓冲的通道和无缓冲的通道之间的一个很大的不同:无缓冲的通道保证进行发送和接收的 goroutine 会在同一时间进行数据交换;有缓冲的通道没有这种保证。

在无缓冲通道的基础上,为通道增加一个有限大小的存储空间形成带缓冲通道。带缓冲通道在发送时无需等待接收方接收即可完成发送过程,并且不会发生阻塞,只有当存储空间满时才会发生阻塞。同理,如果缓冲通道中有数据,接收时将不会发生阻塞,直到通道中没有数据可读时,通道将会再度阻塞。

无缓冲通道保证收发过程同步。无缓冲收发过程类似于快递员给你电话让你下楼取快递,整个递交快递的过程是同步发生的,你和快递员不见不散。但这样做快递员就必须等待所有人下楼完成操作后才能完成所有投递工作。如果快递员将快递放入快递柜中,并通知用户来取,快递员和用户就成了异步收发过程,效率可以有明显的提升。带缓冲的通道就是这样的一个“快递柜”。

image.png

A 子协程发送0~9数字 B 子协程计算输入数字的平方 主协程输出最后的平方数


import "fmt"

func CalSquare(){
src:=make(chan int)
dest:=make(chan int,3)
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{
  fmt.Println(i)
}
}
func main() {
    CalSquare()
}

5.并发安全Lock

对变量进行2000次+1操作,5个协程并发执行


import ("fmt"
"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)
fmt.Println("WithLock:",x)
}
func main() {
  Add()
}

6.WaitGroup

"快速"打印hello goroutine:0~hello goroutine :4 的优化

WaitGroup 结构体提供了三个方法,Add、Done、Wait,Add 的作用是用来设置WaitGroup的计数值(子goroutine的数量);Done的作用用来将 WaitGroup 的计数值减 1,其实就是调用Add(-1);Wait 的作用是检测 WaitGroup 计数器的值是否为 0,如果为 0 表示 goroutine 都运行完成,否则会阻塞等待计数器的值为0(所有的 groutine都执行完成)之后才运行后面的代码。
所以在 WaitGroup 调用的时候一定要保障 Add 函数在 Wait 函数之前执行,否则可能会导致 Wait 方法没有等到所有的结果运行完成而被执行完。也就是我们不能在 Grountine 中来执行 Add 和 Done,这样可能当前 Grountine 来不及运行,外层的 Wait 函数检测到满足条件然后退出了。


import ("fmt"
"sync"
)
func hello(i int){
  println("hello goroutine:"+fmt.Sprint(i))
}
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()
}
func main() {
    ManyGoWait()
}

6.GO依赖管理

(1)GOPATH

image.png

image.png (2) Go Vendor

image.png

image.png (3) Go Module

image.png

image.png

依赖管理-go.mod

image.png 如上图是一个go.mod文件,go 1.16表示原生库版本号,go.mod 文件内提供了module, require、replace和exclude四个关键字

  • module语句指定包的名字(路径)
  • require语句指定的依赖项模块
  • replace语句可以替换依赖项模块
  • exclude语句可以忽略依赖项模块

依赖配置-version

语义化版本组成:major是属于一个大版本,不同major的版本是不兼容的,可以理解为代码隔离,minor通常是新增一些函数或功能,保持major下做到前后兼容,patch通常是代码bug的修复。

基于commit伪版本:首先第一部分是版本前缀和语义化版本组成一样,第二部分是一个时间戳,第三部分是提交commit的12为哈希码。 image.png

依赖配置-indirect

image.png 在go.mod中对于没有直接依赖的,直接导入的模块,就会标识为非直接依赖//indirect标识

依赖配置-incompatible

image.png

依赖配置-依赖图

B image.png

依赖分发-回源

image.png

依赖分发-Proxy

image.png

依赖分发-变量 GOPROXY

image.png

**7.工具 **

go get image.png

go mod

image.png