[ Go依赖管理与工程实践 | 青训营笔记 ]
这是我参与「第五届青训营」伴学笔记创作活动的第 2 天
前言
今日学习了Go语言进阶与依赖管理和Go语言工程实践之测试,写一下我认为的重点。
一.本课重点
并发的深入 1.Goroutine 2.CSP 3.Channel 4.Lock 5.WaitGroup
依赖管理方面 1.GoPATH 2.Go Vendor 3.Go Module
二.详细内容
1.Goroutine
与其他大部分语言提供的协程支持相同,Go 的 Goroutine 是用户态的,其协程栈占用仅有 KB 级别,十分节约系统资源;但不同的是,Goroutine 将协程和并发简化到了仅需一个 go 关键字即可完成,而不像其他语言的协程一样及其繁琐复杂。Goroutine 是有栈协程,而不是如 Kotlin 协程那样的无栈协程。
接下来,让我们看一个典型的 Goroutine 例子,来感受一下 Goroutine 的魅力:
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
time.Sleep(time.Second)
}
上文的代码中,定义了一个 say 函数,接受一个字符串形参,作用是每隔 100 毫秒打印一次传入的字符串,重复 5 次;接下来,在 main 函数,调用了两次 say 函数,并传入不同的参数 "hello" 和 "world",最后,调用 time.Sleep() 函数令当前协程(也就是 main 函数的主协程)休眠 1 秒。与以往不同的是,第一个 say 函数前被标上了一个 go 关键字 —— 这意味着该函数将在一个新的 Goroutine 协程中运行。
输出为
hello
world
world
hello
hello
world
world
hello
hello
2.WaitGroup
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
time.Sleep(time.Second)
}
func main() {
HelloGoRoutine()
}
一个简单的协程程序,其中 HelloGoRoutine 函数开启了 5 个 go 协程,用于打印输出 1-5 的数字,最后,对主协程进行休眠以确保子协程执行完成。
3.Channel
你是否有想过如何在协程与协程之间进行数据传输和通信?你可能会说,这不是很容易的事吗,直接维护一个数组或者结构体实例之类的,然后不同协程按需取用内部的数据就行啦。但是事实上,这种通过共享内存实现的数据通信是十分危险的,可能会遇到位置的问题。Go 语言建议我们通过其内置的 通道(Channel) 功能来进行数据通信,从而共享内存。
可以通过如下方式声明一个 Channel:
ch := make(chan int)
复制代码
声明了一个名为 ch 的无缓冲 Channel,并指定 Channel 的传输数据类型为 int;
无缓存 Channel 意味着,一个数据的发送必须等待另一端代码的接收,如果没有人接收发送的数据,那么发送端便会被永远阻塞。
可以通过如下方式声明一个带缓冲区的 Channel:
ch := make(chan int, 3)
复制代码
声明一个名为 ch 的有三个缓冲区的 Channel,并指定 Channel 的传输数据类型为 int;
这意味着,该 Channel 内可以有最多 3 个数据未被接收方接收,此时,发送方可以直接发送数据而不必收到阻塞,如果超出缓冲区(在本例中为发送第四个数据,且缓冲区被前三个数据占满),则依旧会被阻塞。
可以通过如下方式向 Channel 发送数据:
ch <- v // 假设 v 是一个 int 变量
复制代码
然后,通过如下方式从 Channel 中接受数据:
v := <-ch // 赋值给 v 变量
复制代码
可以通过使用 for range 的方式来从一个 Channel 中取出所有数据:
for i := range ch {
fmt.Println(i)
}
复制代码
for range 将会始终读取一个 Channel 中发送的数据,直到该 Channel 被关闭。
当一个 Channel 被使用完毕,应当调用 close 函数关闭 Channel:
close(ch)
复制代码
以下代码是一个由三个协程组成的程序:第一个协程负责生成 0-9 的数字并通过 Channel 发送给第二个协程;第二个协程接收收到的数字,并将数字进行平方计算,然后将结果发送给主协程;主协程遍历接收到的结果并输出:
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)
}
}
4.依赖管理
GOPATH
是一个环境变量,其中有三个部分:
- bin:项目编译的二进制文件
- pkg:项目编译的中间产物,加速编译
- src:项目源码,项目代码直接依赖src下的代码
go get下载最新版本的包到src目录下
存在的弊端:无法实现package的多版本控制
Go Vender
项目目录下增加vender文件,所有依赖包副本形式放在$ProjectRoot/vender
依赖寻址方式:vender -> GOPATH
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
存在的弊端:更新项目的时候可能导致编译错误的冲突;无法控制依赖的版本。
Go Module(1.16以后默认开启)
- 通过
go.mod文件管理依赖包版本 - 通过
go get/go mod指令工具管理依赖包
终极目标:定义版本规则和管理项目依赖关系
三.实践例子
虽然有实践例子,但对于小白我来说有点难以理解与操作。
四.个人总结
第二天的内容比第一天的内容多了好多,自己原先只会写一些小的程序,然后接触这些对于我来说有点困难与难以接受。唉,真的感觉到了抽象。
五.引用参考
- 字节内部课:Go 语言入门 - 工程实践
- Go 并发 | 菜鸟教程 (runoob.com)