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

165 阅读3分钟

一、语言进阶

从并发编程的视角了解Go语言高性能的本质。

1、并发 VS 并行

Go语言是为并发而生的,可以充分发挥多核优势,实现高效运行。

(1)并发

多线程程序在一个核的cpu上运行,宏观看是多个线程在同一时间间隔运行,微观看是线程在同一时刻串行运行。

(2)并行

多线程程序在多个核的cpu上运行,实现真正的多个线程同一时刻运行。

image.png

2、协程Goroutine

线程在系统态中,它的创建、切换、停止都属于系统操作,因此比较消耗资源。

协程可以理解为轻量级的线程,协程的创建和调度由Go语言本身去完成。

线程可以并发地去跑多个协程!

image.png

(1)实际应用

快速打印hello goroutine:0-4

因为要求快速,所以我们要开启多个协程去打印。

Go语言开启协程很简单,只需要在函数前加上go关键字即可。

image.png

输出:

image.png

3、协程间的通信CSP(Communicating Sequential Processes)

Go语言是通过通信来共享内存,而不是通过共享内存来实现通信!!

image.png

4、通道

通过make关键字进行通道创建。

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

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

无缓冲通道也被称为同步通道,可以实现发送的协程和接受的协程同步化。

解决同步问题的方法就是使用有缓冲通道,它不会保证顺序性。

有缓冲通道可以类比于学校的菜鸟驿站货架,货架格子满了以后会引起阻塞,需要我们及时取走快递才能重新将新的快递放至货架上。

image.png

For example:

A协程发送0-9的数字;B子协程计算输入数字的平方;主协程输出最后的平方数。

package main

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

}

func main() {
	CalSquare()
}

image.png

5、并发安全Lock

因为Go语言存在通过共享内存实现通道,因此会出现多种路径同时操作同一块内存的情况。

通过对临界区加锁来保证并发安全。

For example:

对变量执行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()
}

输出:

image.png

6、WaitGroup

WaitGroup也在sync下,可以用来实现并发任务的同步。

它的内部维护了一个计数器,开启协程时+1,执行结束时-1,当为0时表示所有的并发任务已经完成。

image.png

For example:

package main

import (
	"fmt"
	"sync"
)

func hello(i int) {
	println("hello goroutine:" + fmt.Sprint(i))
}
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()
}

func main() {
	ManyGoWait()
}

输出:

image.png

二、依赖管理

1、依赖管理演进

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

image.png

(1)GOPATH

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

项目的代码直接依赖src下的代码,可以通过go get下载最新的包到src目录下。

  • bin:项目编译的二进制文件
  • pkg:项目编译的中间产物,加速编译
  • 项目源码

弊端:

无法实现package的多版本控制。如图,V2版本的package可能并没有兼容V1版本,这样会导致Project A和ProjectB可能无法构建成功。

image.png

(2)Go Vendor

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

image.png

弊端:

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

同样是依赖源码,并不能很清晰地标识依赖地版本。

image.png

(3)Go Module

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

2、依赖管理三要素

  • 配置文件,描述依赖——go.mod
  • 中心仓库管理依赖库——proxy
  • 本地工具——go get/go mod

3、依赖配置

(1)go.mod

由三部分组成:模块路径、原生库以及单元依赖。

image.png

(2)version版本号

①语义化版本

MAJOR.{MAJOR}.{MINOR}.${PATCH}

  • v :所有版本号都以 v 开头。
  • MAJOR 主版本号:意味着有大的版本更新,一般会导致 API 和之前版本不兼容。
  • MINOR 次版本号:当做了向下兼容的功能性新增及修改。这里有个不成文的约定需要你注意,偶数为稳定版本,奇数为开发版本。
  • PATCH 修订版本号:用户做了向后兼容的 bug 修复。

②基于commit位版本

  • 基本版本前缀:通常为 vX.0.0 或 vX.Y.Z-0。vX.Y.Z-0 表明该 commit 快照派生自某一个语义版本,vX.0.0 表明该 commit 快照找不到派生的语义版本;
  • 时间戳:格式为“yyyymmddhhmmss”,它是创建 commit 的 UTC 时间;
  • 最后是长度为 12 的commit号。

(3)indirect关键字

没有直接依赖的模块会用indirect进行标识。

比如A->B->C,A->B是直接依赖,而A->C是间接依赖。

image.png

(4)incompatible关键字

  • 主版本在V2以上的模块会在模块路径后增加/vN后缀
  • 对于没有go.mod文件且主版本在V2以上的依赖,会加上incompatible

(5)依赖图

如图,若X项目依赖了A1.2、B1.2两个项目,且A1.2、B1.2分别依赖了C项目的v1.3和v1.4两个版本,最终编译时所使用的C项目的版本时v1.4!!

因为每次会选择版本最低的兼容版本,比如现在依赖的C项目有v1.5版本,则选择v1.5版本,以此类推。

image.png

(6)依赖分发-回源

依赖分发是指我们的依赖在哪里去下载并且如何去下载。

直接使用版本管理仓库下载以来的话会存在以下问题:

①无法保证构建稳定性

比如我们依赖的仓库作者是可以直接在代码平台进行增加/修改/删除软件版本,这样就可能导致我们无法找到之前的依赖版本

②无法保证依赖可用性

作者可以删除软件

③增加第三方压力

代码托管平台负载问题

(7)依赖分发-proxy

image.png

(8)依赖分发-变量GOPROXY

go.mod是通过GOPROXY环境变量来控制proxy的配置。

GOPROXY其实是URL列表,direct是指前面的站点都没有依赖的话会回源到第三方。

GOPROXY="proxy1.cn,http://proxy2.cn,…"

(9)go get工具

image.png

(10)go mod工具

image.png

三、第二天心得

今天要比昨天时间更足一些,所以学习也能更深入一些。 这一节主要讲了Go语言的依赖管理,一定要牢记依赖管理三要素:go.mod、proxy和go get/go mod工具。