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

44 阅读2分钟

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

Go语言进阶与依赖管理

本堂重点

并发与并行、依赖管理。

并发 vs 并行

并发:多线程程序在一个核的cpu上运行

并行:多线程程序在多个核的cpu上运行

Go可充分发挥多核优势!

Goroutine

协程与线程

image.png

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

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

协程的使用

例子:快速打印goroutine:0~goroutine:4

func GoRoutine(){
    for i := 0; i < 5; i++ {
        go func(j int) {
            hello(j)
        }(i)
    }
    time.Sleep(time.Second)
}

在调用函数时,在函数前面加入go关键字!(并行打印)

CSP(Communicating Sequential Process)

Go提倡通过通讯共享内存

image.png

但是Go也保留着通过共享内存而实现通讯的机制。

Channel

Channel的创建需要make关键字:make(chan 元素类型, [缓冲区大小]

make(chan int)
或者
make(chan int, 2)

无缓冲通道缓冲区大小为1,有缓冲通道缓冲区大小大于1。

image.png

无缓冲通道,也被称为同步通道,

使用有缓冲通道,能解决同步问题,通道容量就代表着通道中能存储的元素(类比学校仓库,货架格子),其实他也是个典型的生产消费模型。

下面是一个channel的例子:

我们规定:A子协程发送数字0~9,B子协程计算输入数字的平方,主协程输出最后的平方数。

func CalSquare() {
    src := make(chan int)    //no buffer queue
    dest := make(chan int, 3)    //buffer queue
    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)
    }
}

具有顺序性,是并发安全的!

为什么我们的src是用的无缓冲,而dest用的是有缓冲队列呢???

这就可以类比我们生产和消费,一般来说,消费的速度比生产的速度要慢,所以我们这样子设置的话,我们消费者的消费速度问题就不会影响生产者的生产效果。(缓冲解决生产和消费速度不均衡的问题)

这是一个使用通讯来实现共享内存的实践。

并发安全 Lock (对临界区的保护)

我们使用加锁来保证共享内存的并发安全。

并发安全问题在实际开发中的出现是随机的,不是一定的,所以为了排除这一问题,要记得做一些读写操作。

如何使用Lock? (如下)

var (
    x    int64
    lock    sync.Mutex
)

func addWithLock() {
    for i := 0; i < 2000; i++ {
        lock.Lock()    //加锁
        x += 1
        lock.Unlock()    //释放
    }
}

WaitGroup

WaitGroup 包含三个方法,计数器+delta(Add)、计数器-1(Done)、阻塞直到计数器为0(Wait)。当计数器为0时,意味着所有子协程都已完成。

比如我们这节关于Goroutine的第一个例子,如果使用WaitGroup则如下所示:

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

小结

三个要点,Goroutine协程、Channel、Sync,Goroutine协程有利于一些高并发的操作,Channel则体现出了Go通过通讯实现共享内存的优点,最后是Sync包含的一些关键字,包括Lock,WaitGroup等,主要是为了实现并发安全操作和协程间的同步。

依赖管理

学会站在巨人的肩膀上!!!

为什么?第一,我们不可能整个项目工程基于标准库从0到1编码搭建,第二我们需要进行对依赖包的管理。

Go依赖管理演进

GOPATH -> Go Vendor -> Go Module

不同环境(项目)以来的版本不同,我们要控制依赖管理的版本。

环境变量$GOPATH

213e14443873a3d35717ce85207e39b.jpg

项目代码都直接依赖于 src 下的代码,通过 go get 下载最新版本的包到 src 目录下。

弊端??

b4755fdf2f0d8ba732d13dedf772d05.jpg

场景:A项目和 B项目依赖于某一 package 的不同版本,就无法实现package的多版本控制。

Go Vendor

image.png

项目目录下增加vendor文件,所有依赖包副本形式放在 $ProjectRoot/vendor。

依赖寻址方式是:vendor => GOPATH,通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。

Go Vendor的弊端?

image.png

问题:无法控制依赖版本,更新项目有可能出现依赖冲突,导致编译错误。

Go Module

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

管理依赖三要素

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

2.中心仓库管理依赖库 Proxy

3.本地工具 go get/mod

go.mod

image.png

依赖配置-version的两种形式:

image.png

一个关于依赖配置的问题:

image.png

选择最低兼容版本!(有1.5会选1.5)

依赖分发-回源 and Proxy

image.png

实际上,问题就是我们从哪里去下载依赖包。

我们可以直接从对应仓库中下载依赖来完成依赖分发,但是直接使用版本管理仓库的话会存在以下问题:

1.无法保证构建稳定性(增加/删除/修改软件版本)

2.无法保证依赖可用性(删除软件)

3.增加第三方压力(代码托管平台问题)

为了解决这个问题,Goproxy出现啦(实现可靠的稳定的依赖分发)!

image.png

工具使用

go get

image.png

go mod

image.png

(建议在每次项目结束之后来一下 go mod tidy~)

小结

管理依赖这块主要讲了 go.mod 配置文件、中心管理仓库Proxy和服务的管理工具go get和go mod。