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

68 阅读4分钟

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

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

1. 并发编程简述

并发是指多线程在一个核的cpu上运行,通过时间片的切换实现同时运行

并行是利用多核实现多线程运行

1.1Goroutine

线程:内核态,线程跑多个协程,栈KB级别

协程:用户态,轻量级线程,栈MB级别

在调用函数前面加一个go 关键字表示为一个函数创建一个协程来运行

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

1.2 CSP

提倡通过通信来共享内存而不是通过共享内存实现通信

通过共享内存实现通信利用互斥量对内存进行加锁,一定程度上会影响程序性能

1.3 Channel

引用类型,通过make(chan 元素类型,[缓冲大小])创建

根据缓冲大小分为有无缓冲通道

make(chan int)

make(chan int,2)

无缓冲通道

发送Gorountine和接收Gorountine同步化,也被称为同步通道

有缓冲通道

缓冲大小也就是通道容量代表通道中能存多少元素,类比生产消费,货架满了会阻塞发送,直到有人取走才能继续放,有缓冲通道能解决生产和消费速度不均衡带来的执行效率问题

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 {
        println(i)
    }

1.4 并发安全Lock

避免对共享内存进行非并发读写操作可能出错

1.5 WaitGroup

  • Add(delta),计数器+delta;

  • Done(),用来将WaitGroup的计数值减1,其实就是调用了Add(-1);

  • Wait(),调用这个方法的goroutine会一直阻塞,直到WaitGroup的计数值变为0

    利用WaitGroup优化1.1代码

func HelloGoRoutine() {
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(j int) {
            defer wg.Done()
            hello(j)
        }(i)
    }
    wg.Wait()
}

2. 依赖管理

2.1 依赖管理演进

GOPATH->Go Vendor->GoModule

2.1.1 GOPATH

环境变量go项目的工作区

bin 项目编译的二进制文件

pkg 项目编译的中间产物加速编译

src 项目源码

项目代码直接依赖src下的代码

go get下载最新版本的包到src目录下

弊端:A和B依赖于某一package的不同版本,无法实现package多版本控制

2.1.2 Go Vendor

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

弊端:无法控制依赖版本,可能出现依赖冲突

2.1.3Go Module

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

目的:定义版本规则和管理项目依赖关系

2.2 依赖管理三要素

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

2.3 依赖配置

2.3.1 go.mod

module 依赖管理基本单元

go xx.xx go原生库版本号

require() 单元依赖,依赖标识[Module Path] [Version/Pseudo-version]

2.3.2 version
  1. 语义化版本 v1.3.0 v2.3.0 major.minor.patch,不同MJOR间版本隔离,MINOR间版本兼容,PATCH bug修复
  2. 基于commit 伪版本 版本前缀(语义化版本)-时间戳-12位哈希吗前缀
2.3.3 indirect间接依赖

A->B->C A->B直接依赖 A->C间接依赖

2.3.4 incompatible

对于 MAJOR 主版本在 V2 及以上的模块,go.mod 会在模块路径增加 /vN 后缀 。这能让 Go Module 按照不同的模块来处理同一个项目不同 MAJOR 主版本的依赖。

  • 由于 Go Module 是在 Go 1.11 才实验性地引入,所以在这个更新提出之前,已经有一些仓库打上了 V2 或者更高版本的 tag 了。
  • 为了兼容这部分仓库,对于没有 go.mod 文件并且 MAJOR 主版本在 V2 及以上的依赖,会在版本号后加上 +incompatible 后缀。表示可能会存在不兼容的源代码。

依赖关系:选择满足本次构建最低的兼容版本

2.3.5依赖分发-回源

表示依赖去哪里下载如何下载的问题

  • Go 的依赖大部分托管在 GitHub 上。Go Module 系统中定义的依赖,最终都可以对应到 GitHub 中某一项目的特定提交或版本。
  • 对于 go.mod 中定义的依赖,则直接可以从对应仓库中下载指定依赖,从而完成依赖分发。

弊端:

  1. 无法保证构建稳定性 (增加/删除/修改软件版本)
  2. 无法保证依赖可用性
  3. 增加第三方压力(代码托管平台负载问题)
解决方案-Proxy

Go Proxy是一个服务站点,他会缓存源站中的软件/代码内容,且版本不会改变,在作者删除后依然可用,实现了稳定和可靠的依赖分发

使用Go Proxy后,构建时会直接从Go Proxy站点拉取依赖

2.3.6 依赖分发-变量 GOPROXY

Go Module 通过 GOPROXY 环境变量控制如何使用 Go Proxy

GOPROXY 是一个 Go Proxy 站点 URL 列表

GOPROXY = "https://proxy1.cn, https://proxy2.cn, direct"

依赖寻址路径为:先从 proxy1 下载依赖,如果 proxy1 不存在,再从 proxy2 寻找,如果 proxy2 仍不存在,则会回源到源站直接下载依赖,并缓存到 Go Proxy 站点中

2.3.7 工具-go get

go get example.org/pkg

默认go get会拉取major版本最新提交

参数:

  1. @update 默认
  2. @none 删除依赖
  3. @v1.1.2 tag版本,语义版本
  4. @23dfdd5 特定的commit
  5. @master 分支的最新commit
2.3.8 工具-go mod

go mod

参数:

  1. init 初始化,创建go.mod文件(项目开始前必需步骤)
  2. download 下载模块到本地缓存
  3. tidy 增加需要的依赖,删除不需要的依赖