Go 语言进阶与依赖管理 | 青训营笔记

67 阅读2分钟

这是我参与「第五届青训营 」笔记创作活动的第2天

本堂课重点内容

  • Go语言同步机制
  • Go语言依赖管理

详细知识点介绍

Go并发

协程

Go语言支持轻量级、用户态的协程,可以一次创建很多个。要在Go语言中创建一个协程,只需在要执行的函数前加上go即可。

同步

channel

可以使用channel实现线程间的同步。在我看来channel类似于一个生产者-消费者模型,生产者往管道写入,消费者从管道取出。要是管道已满,生产者就阻塞;要是管道为空,消费者就阻塞。

如果channel的缓冲很大的话,就可以看成是一个信号量。写入一个元素就相当于signal,读取一个元素相当于是wait。这样一来就能用channel解决各种同步互斥问题了。(刚刚考完操作系统,强行生搬硬套一下hh)

当然channel在此之外也能够起到进程间通信的效果。

lock

最基本的锁,没啥好说的。Go还支持读写锁。

WaitGroup

可以用于主线程等它创建的多个协程。主线程创建多少个就Add多少,然后用wait阻塞。每个协程执行完后Done一下。所有协程执行完后主线程才继续执行。

Go依赖管理

GoPath

最初的管理方式,所有依赖代码都放在这里的文件夹下面。无法实现多package的版本控制。

Go Vender

依赖放在项目文件夹的vender目录下。对于复杂的依赖关系仍然无法处理。

Go Mod

用项目目录下的go.mod文件维护依赖信息。 常用命令:

  • go mod init:初始化go mod文件
  • go mod tidy:更新go mod文件
  • go mod download:下载缺少的依赖

实践练习例子

为啥需要同步?

func main() {
   a := 0
   var w sync.WaitGroup
   w.Add(1000)
   for i := 0; i < 1000; i++ {
      go func() {
         a++
         w.Done()
      }()
   }
   w.Wait()
   fmt.Println(a)
}

以上这段代码,某次运行结果是987,第二次又变成990了,而且都不等于创建协程的个数1000。这是因为a++其实并不是原子操作,例如它可以被分解为三条指令:

  1. reg <- a
  2. reg <- reg + 1
  3. a <- reg

其中reg是寄存器。倘若a、b两个协程按照a.1 -> b.1 -> a.2 -> b.2 -> a.3 ->b.3的顺序执行,那么在两个协程运行结束后,a的值是a+1而不是a+2。这就是因为缺少同步机制,不同线程的指令交织在一起运行。

解决方法就是引入同步机制,让一个协程完整运行完a++后,再开始下一个。

var l sync.Mutex
for i := 0; i < 1000; i++ {
   go func() {
      l.Lock()
      a++
      l.Unlock()
      w.Done()
   }()
}

但很显然,这会降低程序的并发性,因此需要权衡二者。