Go语言进阶 | 青训营笔记

36 阅读3分钟

并发编程

Goroutine

Go语言中引入了协程概念

image-20230117180153292

func hello(i int) {
  fmt.Println("hello goroutine: " + fmt.Sprint(i))
}
​
func HelloGoRoutine() {
  for i := 0; i < 5; i++ {
    // go 关键字就可以实现
    go hello(i) 
  }
  // 保证子协程执行完之前主协程不退出
  time.Sleep(time.Second)
}

该程序会乱序打印出0-4这五个数字,goroutine只需要在函数前加关键字go即可

CSP

Communicating Sequential Processes

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

image-20230117180540530

Channel

创建channel

make(chan, type, size)

如果是有缓冲通道,就可以指定size的大小,如果是无缓冲通道,则不设置size

下例为子协程发送0~9的数字,主协程输出最后的平方数

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)
  }
}

通过使用通道来保证了生产和消费。使用有缓冲区的dest是因为可能消费者的处理速度(处理逻辑)比较慢,需要使用带缓冲的。

并发安全

多个协程访问同一片内存可能导致访问不安全

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 addWithLock()
  }
  time.Sleep(time.Second)
  fmt.Println("With lock result:", x) //输出结果为10000
  x = 0
  for i := 0; i < 5; i++ {
    go addWithoutLock()
  }
  time.Sleep(time.Second)
  fmt.Println("Without lock result:", x) //随机输出结果
}

通过观察输出结果可以看出,如果不使用lock,最终的结果是一个随机的结果,是因为五个协程并发访问内存区域x导致访问不安全。使用lock后,可以保证x在被一个协程访问的时候不会被其他的影响。

WaitGroup

WaitGroup可以代替第一个例子中的time.Sleep(time.Second)

是一个计数器,开启一个协程+1,执行结束-1,主协程阻塞直到计数器为0。

func hello(i int) {
  fmt.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()
}

依赖管理

Go Module

Go 1.11 版本推出 modules 机制,简称 mod,更加易于管理项目中所需要的模块。模块是存储在文件树中的 Go 包的集合,其根目录中包含 go.mod 文件。 go.mod 文件定义了模块的模块路径,它也是用于根目录的导入路径,以及它的依赖性要求。每个依赖性要求都被写为模块路径和特定语义版本。

通过go.mod文件管理依赖包版本

通过go get/go mod指令工具管理依赖包

go.mod

go.mod用于配置文件,描述依赖

image-20230118110633317

模块路径用来标识一个模块,从模块路径可以看出从哪里找到该模块。如果是github前缀则表示可以从Github仓库找到该模块。

单元依赖中,每个依赖单元用模块路径+版本来唯一标识

单元依赖中的indirect表示非直接依赖

依赖分发

依赖分发指从哪里下载,如何下载

image-20230118111211398

使用Github进行代码托管存在多个问题

使用Go Proxy可以解决上述问题。Go Proxy是一个服务站点,会缓存源站中的软件内容;使用Go Proxy之后,构建会直接从Go Proxy站点拉取依赖

image-20230118111331439

image-20230118111612719

工具

go get

image-20230118111701134

go mod

image-20230118111737138