> 文章列表 > Go 语言进阶与依赖管理

Go 语言进阶与依赖管理

Go 语言进阶与依赖管理

作者:非妃是公主
专栏:《Golang》
博客主页:https://blog.csdn.net/myf_666
个性签:顺境不惰,逆境不馁,以心制境,万事可成。——曾国藩
在这里插入图片描述

文章目录

  • 一、语言进阶
    • 1. 并发和并行
    • 2. 协程(Goroutine)与线程
    • 3. CSP(Communicating Sequential Processes)
    • 4. Channel
    • 5. 并发安全 Lock
    • 6. WaitGroup
  • 二、依赖管理
    • 1. GOPATH
    • 2. Go Vendor
    • 3. Go Module
      • Ⅰ. 依赖表述
      • Ⅱ. 依赖管理
      • Ⅲ. 本地工具
        • ① go get
        • ② go mod

在这里插入图片描述

一、语言进阶

1. 并发和并行

Go语言是一种并发性能很好的语言,它可以多核(多核指:一个电脑包含多个CPU)优势,更加高效地运行。
在这里插入图片描述


2. 协程(Goroutine)与线程

Go语言的并发编程,主要就是通过协程(Goroutine)来实现的。协程和线程类似,但又并不同于线程,它比线程更加轻量级。
在这里插入图片描述

协程:用户态,轻量级线程,栈KB级别;
线程:内核态,线程跑多个协程,栈MB级别。


3. CSP(Communicating Sequential Processes)

协程间通信与进程通信类似,主要有2种方式:

  1. 通道
  2. 临界区

对于这两种方式,Go语言采用第一种——通道方式,进行协程间的信息传递。即,下图中左边的这种方式:
在这里插入图片描述
这样做的优点是,通过通信共享内存性能较高,不会像临界区一样,存在性能较低的问题。

4. Channel

缓冲通道:

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

它可以采用上面make的方式进行初始化,第一个参数指定类型,第二个参数指定缓冲通道的大小。

在这里插入图片描述

一个示例代码如下:

package concurrencefunc CalSquare() {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)}
}

代码中关于defer主要用来进行延迟调用(程序执行完毕后,即将退出时,执行defer,详细的关于defer的介绍,请查看博客:Golang中的一些关键字),然后相当于通过src这一公共区域进行内存通信。

这个demo程序类似于生产者、消费者进程,输出结果如下:

在这里插入图片描述

5. 并发安全 Lock

锁的作用主要是用来保证在操作变量时,只有一个协程在操作,这时候另一个协程想要操作的时候,需要等待锁解除才行。这样的操作,可以保证程序执行的正确性,防止程序出错。

下面来看一个demo,这个demo的作用是执行2000次+1操作,然后采用5个协程进行。函数的定义如下。

首先,是带有锁的函数定义,在操作x的时候,锁住了lock,如下:

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

不带锁的定义,如下:

func addWithoutLock() {for i := 0; i < 2000; i++ {x += 1}
}

5个协程,每个执行2000次+1操作,如下:

func Add() {x = 0for i := 0; i < 5; i++ {go addWithoutLock()}time.Sleep(time.Second)println("WithoutLock:", x)x = 0for i := 0; i < 5; i++ {go addWithLock()}time.Sleep(time.Second)println("WithLock:", x)
}

运行程序如下:

在这里插入图片描述

这时,很有意思的是,并没有发生错误!本来想着由于没有加锁,这样多个进程在操作数据的时候就可能在同一个时钟周期,而造成数据丢失。

于是,我分析了一下原因,现在的CPU为16核,甚至更多,5个协程可能可以在不同的不同核上并行去跑,之间并没有影响。也就是说,会不会出错误,可能和机子的性能有关。我的机子是16核的,因此,我进行了如下尝试,修改协程数为17,果然,触发了错误,没有锁的协程产生了操作丢失。

在这里插入图片描述

6. WaitGroup

相当于一个计数器,那么它的作用是什么呢?记录当前正在执行的协程数,当正在执行的线程数不为 0 的时候,就需要进行一个等待。

demo程序如下:

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

其中 wg 表示创建的一个WaitGroup,通过Add可以添加一个协程数,Add可以为负数;

然后启动协程,通过延迟调用,当协程完成后,我们执行Done函数,这会对写成书 -1 。

最后通过 Wait 函数,如果正在运行的协程不为0(存在正在运行的协程),那么就不结束主进程。

关于WaitGroup的定义,可以查看源码及注释进行了解,如下:
在这里插入图片描述

Done 和 Wait,如下:

在这里插入图片描述

同时,有了 wg.Wait() ,既可以省略了之前的 time.Sleep(time.Second) 了。

二、依赖管理

随着功能的增强,同时也需要更多的依赖库,这时候,依赖管理就显得很重要了。

Golang的依赖管理按照发展历史,可分为3种,GOPATH,Go Vendor,Go Module。如下图:

在这里插入图片描述

1. GOPATH

首先,值得说明的是,不同于Java、C++、python等,在Golang中,是不存在项目(project)这一概念的,在Gopath路径下就是Golang的工作目录,存在三个文件夹,作用分别如图中标注:

在这里插入图片描述

弊端:无法实现package的多版本控制。比如,不同的程序用到了同一个package,但是又是不同版本的,这时候,Gopath这种管理方式就无法处理了。如下:

在这里插入图片描述

2. Go Vendor

由于上面的弊端,诞生了Go Vendor,相当于在每个项目下,建立了一个Vendor文件夹,里面存放项目中用到的package,当程序寻找依赖报的时候,先去Vendor中去寻找,如果找不到,再去Gopath下去寻找,这就避免了上面提到的 package版本 问题。
在这里插入图片描述

但这种方法同样存在着较大的问题,如果一个项目(A)用到的两个包(B和C),这两个包又依赖了同一个包D,但是用到的这同一个包但是版本不同且不兼容的包D,同样回出现如下 无法控制版本 的问题(即项目内出现此问题):

在这里插入图片描述

3. Go Module

Go Module解决了上面相同包,不同版本的问题。

和Mave很类似,其中包含三要素:

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

Ⅰ. 依赖表述

在这里插入图片描述

在这里插入图片描述

通过上面这两种标识,我们就可以将不同版本的包区分开来。

Ⅱ. 依赖管理

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Ⅲ. 本地工具

① go get

在这里插入图片描述

② go mod

在这里插入图片描述

在这里插入图片描述