[ GO语言进阶与依赖管理 | 青训营笔记 ]
标题:Go 语言进阶与依赖管理 - 掘金
一、Go语言进阶
Golang具有高并发性
Golang 通过高效调度,最大利用资源,充分发挥多核计算机的优势。
1.1 Goroutine
1.线程
线程是内核态的,它指的是进程内的一个执行单元,也是进程内的可调度实体。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程上面可以跑多个协程。
2.协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
协程与线程的区别
- 一个线程可以多个协程,一个进程也可以单独拥有多个协程。
- 线程进程都是同步机制,而协程则是异步。
- 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
- 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
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) 工具
- go get go get example.org/pkg
@update 默认
@none 删除依赖
@v1.1.2 tag版本,语义版本
@23dfdd5 指定的commit
@master 分支的最新commit
- go mod
go mod
go mod init 初始化,创建go.mod文件
go mod download 下载模块到本地缓存
go mod tidy 增加需要的依赖,删除不需要的依赖