3.Go语言进阶与依赖管理 | 青训营

59 阅读2分钟

1.语言进阶

1.1 并发编程

并发与并行的区别:

  • 并发是指多线程程序在一个核的CPU上运行,主要通过时间片的切换来实现同时运行的状态。
  • 并行是指多线程程序在多个核的cpu上运行,直接利用多核实现多线程的运行。Go可以充分发挥多核计算机优势,高效运行。

1.1.1 Goroutine

  • 协程:用户态,轻量级线程,栈KB级别
  • 线程:内核态,线程跑多个协程,栈MB级别
  • GO语言一次可以创建上万协程。
package main

import (
   "fmt"
   "time"
)

func main() {
   HelloRoutine()
}
func hello(i int) {
   println("hello, goroutine :" + fmt.Sprint(i))
}
func HelloRoutine() {
   for i := 0; i < 5; i++ {
      go func(j int) {
         hello(j)
      }(i)
   }
   time.Sleep(time.Second)
}

1.1.2 CSP (Communicating Sequential Processes)

协程间的通信:Go提倡通过通信共享内存,而不是通过共享内存而实现通信。

image.png 通过通信共享内存涉及到通道(channel)的概念,通道就像是一个传输队列,遵循先入先出,能保证收发数据的顺序,channel让一个gorountine发送特定的值到另一个gorountine的通讯机制。go也保留着通过共享内存实现通信的机制,必须通过互斥量对内存加锁,获取临界区的权限,一定程度上影响性能。

1.1.3 Channel

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

  • 无缓冲通道 make(chan int) 同步通道
  • 有缓冲通道 make(chan int, 2)

image.png 下面的代码展示了channel的使用 A 子协程发送0~9数字 B 子协程计算输入数字的平方 主协程输出最后的平方数

package main

func main() {
   CalSquare()
}
func CalSquare() {
   src := make(chan int)
   dest := make(chan int, 3)
   go func() { //A 生产
      defer close(src)
      for i := 0; i < 10; i++ {
         src <- i
      }
   }()
   go func() { //B 消费
      defer close(dest)
      for i := range src {
         dest <- i * i
      }
   }()
   for i := range dest { //主协程
      println(i)
   }
}

1.1.4 并发安全Lock

下面的例子对变量执行2000次+1操作,5个协程并发执行。不加锁会输出未知的结果,这就是并发安全问题。通过对临界区的控制来保证并发安全。

package main

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

1.1.5 WaitGroup

  • Add(delta int) 计数器+delta
  • Done() 计数器-1
  • Wait() 阻塞直到计数器为0
  • 计数器 开启协程+1;执行结束-1;主协程阻塞直到计数器为0。
func ManyGoWait() {
   var wg sync.WaitGroup
   wg.Add(5)
   for i := 0; i < 5; i++ {
      go func(j int) {
         defer wg.Done()
      }(i)
   }
   wg.Wait()
}

2. 依赖管理

背景:

  • 工程项目不可能基于标准库0~1编码搭建
  • 管理依赖库

2.1 Go依赖管理演进

GOPATH--->Go Vendor--->Go Module 版本迭代的目的:

  • 不同环境(项目)依赖的版本不同
  • 控制依赖库的版本

2.1.1 GOPATH

  • 环境变量 $GOPATH Go项目的工作区

image.png

  • 项目代码直接依赖src下的代码
  • go get下载最新版本的包到src目录下

GOPATH-弊端

image.png

  • 场景:A和B依赖于某一package的不同版本
  • 问题:无法实现package的多版本控制

2.1.2 Go Vendor

  • 项目目录下增加vendor文件,所有依赖包副本形式放在 $ProjectRoot/vendor
  • 项目寻址方式:vendor=>GOPATH
  • 通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题

Go Vendor-弊端

  • 无法控制依赖的版本
  • 更新项目又可能出现依赖冲突 ,导致编译出错

2.1.3 Go Module

  • 通过 go.mod文件管理依赖包版本
  • 通过 go get/go mod指令工具管理依赖包
  • 终极目标:定义版本规则和管理项目依赖关系

2.2 依赖管理三要素

  1. 配置文件,描述依赖 go.mod
  2. 中心仓库管理依赖库 Proxy
  3. 本地工具 go get/mod

2.2.1 依赖配置-go.mod

image.png

依赖标识:[Module Path][Version/Pseudo-version]

2.2.2 依赖配置-version

语义化版本

${MAJOR}.${MINOR}.${PATCH} 不同MAJOR是不兼容的,MINOR在MAJOR下兼容 PATCH补丁
V1.3.0
V2.3.0

基于commit伪版本

vX.0.0-yyyymmddhhmmss-abcdefgh1234

v0.0.0-20220401081311-c38fb59326b7

v1.0.0-20201130134442-10cb98267c6c

2.2.3 依赖配置-indirect

image.png

2.2.4 依赖配置-incompatible

  • 主版本2+模块会在模块路径增加/vN后缀。
  • 对干没有ao.mod 文件并且主版本2+的依赖,会+incompatible

2.2.5 依赖分发-回源

image.png

  • 无法保证构建稳定性增加/修改/删除软件版本
  • 无法保证依赖可用性删除软件
  • 增加第三方压力,代码托管平台负载问题

2.2.6 依赖分发-Proxy

image.png

2.2.7 依赖分发-变量 GOPROXY

image.png

2.2.8 工具- go get

image.png

2.2.8 工具- go mod

image.png