[GO语言进阶与依赖管理 | 青训营笔记 ]

107 阅读4分钟

[ GO语言进阶与依赖管理 | 青训营笔记 ]

标题:Go 语言进阶与依赖管理 - 掘金

网址:juejin.cn/course/byte…

一、Go语言进阶

Golang具有高并发性

Golang 通过高效调度,最大利用资源,充分发挥多核计算机的优势。

1.1 Goroutine

1.线程

线程是内核态的,它指的是进程内的一个执行单元,也是进程内的可调度实体。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程上面可以跑多个协程。

2.协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。

协程与线程的区别

  1. 一个线程可以多个协程,一个进程也可以单独拥有多个协程。
  2. 线程进程都是同步机制,而协程则是异步。
  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
  4. 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。

3.代码中实现线程的使用

快速打印hello goroutine: 0 ~ 4

package main

import (
	"fmt"
	"time"
)

func main() {
	HelloGoroutine()
}

func hello(i int) {
	println("hello goroutine:" + fmt.Sprint(i))
}
func HelloGoroutine() {
	for i := 0; i < 5; i++ {
// golang 中在调用函数前使用 go 关键字可以创建一个协程
// 这里采用了匿名函数,也就是没有函数名,只有参数
		go func(j int) {
			hello(j)
		}(i)
	}
	time.Sleep(time.Second)
}

1.2 CSP

CSP(Communicating Sequential Processes),这是一个并发的模型。

Go的CSP并发模型,是通过goroutine和channel来实现的

  • goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。

  • channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。

1.3 Channel

通过make(chan 元素类型,[缓冲大小])来创建,比如无缓冲通道采用 make(chan int);有缓冲通道采用make(chan int, 2)

通信机制channel很方便,传数据用channel <- data,取数据用<-channel

在通信过程中,传数据channel <- data和取数据<-channel必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。

而且不管传还是取,必阻塞,直到另外的goroutine传或者取为止。

通过一个代码来实现goroutine以及channel的使用。

package main
 
import "fmt"
 
func main() {
   
   messages := make(chan string)
 
   go func() { messages <- "ping" }()
 
   msg := <-messages
   fmt.Println(msg)
}

1.4 并发安全 Lock

golang中的锁分为互斥锁、读写锁、原子锁即原子操作。在 Golang 里有专门的方法来实现锁,就是 sync 包,这个包有两个很重要的锁类型。一个叫 Mutex, 利用它可以实现互斥锁。一个叫 RWMutex,利用它可以实现读写锁。

全局锁 sync.Mutex,是同一时刻某一资源只能上一个锁,此锁具有排他性,上锁后只能被此线程使用,直至解锁。加锁后即不能读也不能写。全局锁是互斥锁,即 sync.Mutex 是个互斥锁。

互斥锁有两个方法:加锁、解锁。

一个互斥锁只能同时被一个 goroutine 锁定,其它 goroutine 将阻塞直到互斥锁被解锁(重新争抢对互斥锁的锁定)。使用Lock加锁后,不能再进行加锁,只有当对其进行Unlock解锁之后,才能对其加锁。

func (m *Mutex) Lock()
func (m *Mutex) Unlock()

1.5 WaitGroup

Golang 提供了简洁的 go 关键字来让开发者更容易的进行并发编程,同时也提供了 WaitGroup对象来辅助并发控制。

WaitGroup提供了三个方法

// 计数器+delta
Add(delta int)
// 计数器-1
Done()
// 阻塞直到计数器为0
Wait()

计数器开启协程+1;执行结束-1;主协程阻塞直到计数器为0

可以将 1.1 里面的代码使用WaitGroup进行优化。

func goWaitGroup() {
	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()
}

二、Golang的依赖管理

2.1 GOPATH

环境变量 $GOPATH,在GOPATH目录下,有bin(项目编译的二进制文件)pkg(项目编译的中间产物,加速编译)src(项目源码)

GOPATH无法实现package的多版本控制。

2.2 Go Vendor(不建议使用)

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

依赖寻址方式: vendor => GOPATH

Go Vendor 无法控制依赖的版本;而且更新项目又可能出现依赖冲突,导致编译错误。

2.3 Go Moudule(建议使用)

通过 go.mod 文件管理依赖包版本

通过 go get/go mod 指令工具管理依赖包

1) go.mod文件内容

module Ex2/demo 依赖管理基本单元

go 1.19 原生库

require (                 单元依赖
    github.com/q1mi/hello v0.1.1 // indirect
    github.com/docker/docker v20.10.17+incompatible
)

2) 依赖配置-indirect/incompatible

go.mod 文件里的require中对于没有直接表示的依赖,会采用 indirect 间接依赖,直接标识出来

对于一些比较老的项目可能当时go mod还没出现,但版本早已经迭代到v2 以上,或者有些项目没有遵循以上的原则,go mod为了能够正常使用它们,会在引入 v2 以上的版本后加上 +incompatible 以示提醒。

3) go module相关指令

命令介绍
go mod init初始化项目依赖,生成go.mod文件
go mod download根据go.mod文件下载依赖
go mod tidy对比项目文件中引入的依赖与go.mod进行对比
go mod graph输出依赖关系图
go mod edit编辑go.mod文件
go mod vendor将项目的所有依赖导出至vendor目录
go mod verify检验一个依赖包是否被篡改过
go mod why解释为什么需要这个依赖

4) 使用go module下载依赖包

下载依赖包的两种方法

一: 在项目目录下执行 go get 命令手动下载依赖的包,默认下载最新的发布版本。

go get -u github.com/q1mi/hello
go get: added github.com/q1mi/hello v0.1.1

也可以指定想要下载指定的版本号。

go get -u github.com/q1mi/hello@v0.1.0
go: downloading github.com/q1mi/hello v0.1.0
go get: downgraded github.com/q1mi/hello v0.1.1 => v0.1.0

如果想指定下载某个 commit 对应的代码,可以直接指定commit hash,一般写出前7位即可。

go get github.com/q1mi/hello@2ccfadd
go: downloading github.com/q1mi/hello v0.1.2-0.20210219092711-2ccfaddad6a3
go get: added github.com/q1mi/hello v0.1.2-0.20210219092711-2ccfaddad6a3

二: 直接编辑go.mod文件

module holiday

go 1.16

require github.com/q1mi/hello latest

表示当前项目需要使用 github.com/q1mi/hello 库的最新版本,然后在项目目录下执行 go mod download 下载依赖包

可以在 go.mod 文件中指定需要的版本进行下载,只用指定commit hash的前7位就好了。

require github.com/q1mi/hello 2ccfadda

Go语言支持在一个项目(project)下定义多个包(package)。

例如,我们在holiday项目内部创建一个新的package-summer,此时新的项目目录结构如下:

holidy
├── go.mod
├── go.sum
├── main.go
└── summer
    └── summer.go

其中 holiday/summer/summer.go 文件内容如下:

package summer

import "fmt"

//Diving潜水
func Diving() {
    fmt.Println("夏天去诗巴丹潜水...")
}

当我们需要调用的时候

package main

import (
    "fmt"

    "holiday/summer"
)

func main() {
    summer.Diving()
}

我们可以在holidy/go.mod文件中正常引入liwenzhou.com/overtime包,然后像下面的示例那样使用replace语句将这个依赖替换为使用相对路径表示的本地包。

module holiday

go 1.16


require github.com/q1mi/hello v0.1.1

replace liwenzhou.com/overtime => ../overtime

5) GOPROXY

这个环境变量主要是用于设置Go模块代理(Go module proxy),其作用是用于使Go在后续拉取模块版本时能够脱离传统的VCS方式,直接通过镜像站点来快速拉取。

设置GOPROXY的命令如下:

go env -v GOPROXY=https://goproxy.cn,dirct

6) 工具

  1. go get go get example.org/pkg
@update 默认
@none 删除依赖
@v1.1.2 tag版本,语义版本
@23dfdd5 指定的commit
@master 分支的最新commit
  1. go mod

go mod

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