Go语言入门-工程实践-GoModule | 青训营笔记

62 阅读3分钟

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

本堂课重点内容

  • 语言进阶:了解Go高性能本质
  • 依赖管理:了解Go语言依赖管理的演进路线

1.语言进阶

1.1 Goroutine

协程与线程
协程:

  • 用户态
  • 一个线程跑多个协程
  • 栈KB级别

线程:

  • 内核态
  • 栈MB级别

语法
go 函数调用即可开启一个goroutine

1.2 CSP(Communicating Sequential Process)

两种通信方式

  • 通过通道通信共享内存
  • 通过共享内存实现通信

image.png 提倡通过通道通信共享内存而不是后者

原因如下:

  • 共享内存需要不同goroutine访问同一个临界区,访问临界区需要加锁,降低并行的效率

1.3 Channel

Go中的通道,用于goroutine间的通信

分为有缓冲区无缓两种

创建

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

// 有缓
make(chan int)

// 无缓
make(chan int, 2)

区别

  • 无缓冲通道会使得goroutine双方同步化,故称为同步通道
  • 有缓冲区的通道是一个典型的生产者-消费者模型
  • 当缓冲区满了以后,直到有goroutine从中取走数据,发送方都无法继续发送从而阻塞
  • 同理,当缓冲区空的时候,直到有goroutine向通道发送数据,接收方都无法继续接收从而阻塞

使用

src := make(chan int)

go func(data []int){
    defer close(src)
    for _, item := range data{
        src ← item
    }
}()

go func(){
    for item := range src{
        // 使用数据
    }
}

1.4 并发安全Lock

对临界区的操作必须要加锁,否则结果无法预计

WaitGroup

作用:阻塞直到所有调用它的goroutine完成后,才继续执行后面的程序

涉及标准库:sync

三个公开的方法:

  • Add(delta int) // wg计数器+delta
  • 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() // goroutine结束后,调用使得计数器-1
            Println(j)
        }(i)
    }
    wg.Wait() // 阻塞 直到所有goroutine结束
}

2.依赖管理

背景动机:

  • 工程项目需要借助外部库搭建
  • 管理所依赖外部库的版本

2.1 依赖管理演进

2.1.1 GOPATH

原理

  • GOPATH是Go语言支持的一个环境变量,值是项目工作区

文件结构
$GOPATH

  • bin // 可执行文件
  • pkg // 项目编译中间产科,加速编译
  • src // 项目源码

结果

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

弊端

  • 无法实现package的多版本控制 image.png

2.1.2 Go Vendor

原理

  • 项目目录下增加vendor文件,所有依赖包以副本形式放在$ProjectRoot/vendor下
  • 依赖寻址方式:优先vendor文件,再GOPATH

文件结构
$ProjectRoot

  • vendor
  • READEME.md
  • main.go
  • service

结果

  • 通过每个项目引入一份依赖的副本,解决了多个项目需要依赖同一个package的版本冲突问题

弊端

  • 无法控制依赖的版本
  • 更新项目又可能出现依赖冲突,导致编译出错
  • 情景:项目内多个包依赖同一个包的不同版本

2.1.3 Go Module

原理

  • 通过go.mod文件管理依赖包版本
  • 通过go get/go mod指令工具管理依赖包

2.2 依赖管理三要素

依赖管理三要素

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

2.3 Go Module介绍

2.3.1 依赖配置go.mod

配置

  • 依赖管理基本单元(可以视为项目根目录)
  • 原生库:指定Go版本
  • 单元依赖:每个依赖单元用模块路径+版本号唯一标识
    • indirect表示非直接引用:A→B→C(A间接引用了C)
    • +incompatible
      • 主版本2+模块会在模块路径增加/vN 后缀
      • 对于没有 go.mod 文件并且主版本2+的依赖,会+incompatible

2.3.2 依赖配置-version

语义化版本:${MAJOR}.${MINOR}.${PATCH}

  • V1.3.0
  • V2.4.0

基于commit的伪版本:vX.0.0-yyyymmddhhmmss-abcdefgh1234

  • v0.0.0-20230117100156-dasdasda2313
  • v1.0.0-20240618125645-cxzbnmbm4547

2.3.3 依赖分发-回源

image.png 直接使用版本仓库的下载管理存在的问题

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

依赖分发-Proxy

image.png

2.3.6 依赖分发-变量 GOPROXY

优先级递减:Proxy 1→Proxy 2→Direct(源站) Proxy 1/2指服务站点URL列表 从源站下载的依赖会缓存到proxy站点中

2.3.7 工具-go get

go get example.org/pkg@

  • update 默认
  • none 删除依赖
  • v1.1.2 tag版本,语义版本
  • 23dfdd5 特定的commit
  • master 分支最新的commit

go mod

  • init 初始化 建立go.mod文件
  • download 下载模块到本地缓存
  • tidy 增加需要的依赖,删除不需要的依赖