这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
并发与并行
协程Goroutine
读者应注意:上图中下面所写文字介绍中,线程与协程的定义写反了。
通过go关键字来开启goroutine;
goroutine是一个轻量级线程,其调度是由Golang运行时进行管理的。
package main
import (
"fmt"
"time"
)
func HelloPrint(i int) {
// println("Hello goroutine : " + fmt.Sprint(i))
fmt.Println("Hello goroutine :", i)
}
// 效果就是快速且无序打印
func HelloGoroutine() {
for i := 0; i < 5; i++ {
// 匿名函数是为了保护变量(大概)
go func(j int) {
HelloPrint(j)
}(i)
}
// time.Sleep()的作用是:保证了子协程在执行完之前,主协程不退出。
time.Sleep(time.Second)
}
func main() {
HelloGoroutine()
}
Channel
通道是用来传递数据的一个数据结构,可以用于两个goroutine之间,通过传递一个指定类型的值来同步运行和通讯。
操作符<-用于指定通道的方向,实现发送or接收;
特别地,若未指定方向,则为双向通道;
若想要使用通道:
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据 并把值赋给 v
下面是一个完整的例子:
package main
import (
"fmt"
)
func CalcPow() {
src := make(chan int)
dest := make(chan int, 3)
// 子协程src发送0~9数字
go func() {
defer close(src) // 当子协程src结束的时候再关闭,减少资源浪费
for i := 0; i < 10; i++ {
src <- i
}
}()
// 子协程dest计算输入数字的平方
go func() {
defer close(dest)
// 通过 range 关键字来实现遍历读取到的数据
for i := range src {
dest <- (i * i)
}
}()
// 主协程输出最后的答案
// 这里可以暂时认为子协程需要使用匿名函数
for i := range dest {
// 因为主协程可能会有更多的复杂操作,比较耗时,所以用带缓冲的通道可以避免问题
fmt.Println(i)
}
}
func main() {
CalcPow()
}
下面为通过阅读其他同学的文章所得知:
如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
range 函数遍历每个从通道接收到的数据,因为 src 在发送完 10个数据之后就关闭了通道,所以这里我们 range 函数在接收到 10个数据之后就结束了。如果上面的 src 通道不关闭,那么 range 函数就不会结束,从而在接收第 11个数据的时候就阻塞了。
defer语句
加上defer之后就会进入栈内,如下程序输出为333 222 111
package main
import "fmt"
func main() {
defer fmt.Println("111")
defer fmt.Println("222")
defer fmt.Println("333")
}
init函数:
程序初始化顺序:变量初始化->init()->main()
package main
import "fmt"
var v int = VarInit()
func init() {
fmt.Println("INITIALIZE!")
}
func VarInit() int {
fmt.Println("The initialize of var is completed!")
return 110
}
func main() {
fmt.Println(v)
}
/*
Output:
The initialize of var is completed!
INITIALIZE!
110
*/
Gopath是一个环境变量,其中有三个部分:
- bin:项目编译的二进制文件
- pkg:项目编译的中间产物,加速编译
- src:项目源码,项目代码直接依赖src下的代码
go get下载最新版本的包到src目录下
存在的弊端:无法实现package的多版本控制
Go Vendor
项目目录下增加vender文件,所有依赖包副本形式放在$ProjectRoot/vender
依赖寻址方式:vender -> GOPATH
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
存在的弊端:更新项目的时候可能导致编译错误的冲突;无法控制依赖的版本。
Go Module(1.16以后默认开启)
- 通过
go.mod文件管理依赖包版本 - 通过
go get/go mod指令工具管理依赖包
终极目标:定义版本规则和管理项目依赖关系
依赖三要素:
- 配置文件,描述以来——
go.mod - 中心仓库管理依赖库——
Proxy - 本地工具——
go get/mod