Golang
并发编程
goroutine
创建 goroutine:
// 子routine
func newTask() {
i := 0
for {
i++
fmt.Printf("new Goroutie: i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
// 主routine
func main() {
// 创建一个子进程 去执行newTask()流程
go newTask()
i := 0
for {
i++
fmt.Printf("main goroutine: i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
退出当前的 goroutine 的方法 runtime.Goexit()
,比较以下两段代码:
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
fmt.Println("B")
}()
fmt.Println("A")
}()
// 防止程序退出
for {
time.Sleep(1 * time.Second)
}
}
执行了退出 goroutine 的方法:
func main() {
go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
runtime.Goexit() // 退出当前goroutine
fmt.Println("B")
}()
fmt.Println("A")
}()
// 防止程序退出
for {
time.Sleep(1 * time.Second)
}
}
channel
channel 用于在 goroutine 之间进行数据传递:
make(chan Type) // 等价于 make(chan Type, 0)
make(chan Type, capacity)
channel <- value // 发送value到channel
<-channel // 接收并将其丢弃
x := <-channel // 从channel中接收数据,并赋值给x
x, ok := <-channel // 功能同上,同时检查通道是否已关闭或为空
channel 的使用:
func main() {
// 定义一个channel
c := make(chan int)
go func() {
defer fmt.Println("goroutine 结束")
fmt.Println("goroutine 正在运行")
c <- 666 // 将666发送给c
}()
num := <-c // 从c中接受数据, 并赋值给num
fmt.Println("num = ", num)
fmt.Println("main goroutine 结束...")
}
上面的代码(使用 channel 交换数据),sub goroutine 一定会在 main goroutine 之后运行
- 如果 main goroutine 运行的快,会进入等待,等待 sub goroutine 传递数据过来
- 如果 sub goroutine 运行的快,也会进入等待,等待 main routine 运行到当前,然后再发送数据
无缓冲的 channel
-
第 1 步,两个 goroutine 都到达通道,但哪个都没有开始执⾏发送或者接收。
-
第 2 步,左侧的 goroutine 将它的⼿伸进了通道,这模拟了向通道发送数据的⾏为。
这时,这个 goroutine 会在通道中被锁住,直到交换完成。
-
第 3 步,右侧的 goroutine 将它的手放⼊通道,这模拟了从通道⾥接收数据。
这个 goroutine ⼀样也会在通道中被锁住,直到交换完成。
-
第 4 步和第 5 步,进⾏交换。
-
第 6 步,两个 goroutine 都将它们的手从通道里拿出来,这模拟了被锁住的 goroutine 得到释放。
两个 goroutine 现在都可以去做其他事情了。
有缓冲的 channel
-
第 1 步,右侧的 goroutine 正在从通道接收一个值。
-
第 2 步,右侧的这个 goroutine 独立完成了接收值的动作,左侧的 goroutine 正在发送一个新值到通道里。
-
第 3 步,左侧的 goroutine 还在向通道发送新值,⽽右侧的 goroutine 正在从通道接收另外一个值。
这个步骤⾥的两个操作既不是同步的,也不会互相阻塞。
-
第 4 步,所有的发送和接收都完成,⽽通道里还有⼏个值,也有一些空间可以存更多的值。
特点:
- 当 channel 已经满,再向⾥面写数据,就会阻塞。
- 当 channel 为空,从⾥面取数据也会阻塞。
关闭 channel
channel 不像文件一样需要经常去关闭,只有当确实没有任何发送数据了,或者想显式的结束 range 循环之类的,才去关闭 channel,注意:
- 关闭 channel 后,无法向 channel 再发送数据(引发 panic 错误后导致接收立即返回零值)
- 关闭 channel 后,可以继续从 channel 接收数据
- 对于 nil channel,⽆论收发都会被阻塞
channel 与 range
func main() {
c := make(chan int)
go func() {
defer close(c)
for i := 0; i < 5; i++ {
c <- i
}
}()
// 可以使用range来迭代不断操作channel
for data := range c {
fmt.Println(data)
}
fmt.Println("Main Finished..")
}
channel 与 select
select 可以用来监控多路 channel 的状态
Go Modules
Go modules 是 Go 语言的依赖解决⽅案。
发布于 Go1.11,成⻓于 Go1.12,丰富于 Go1.13,正式于 Go1.14 推荐在生产上使⽤。
Go modules 集成在Go 的工具链中,只要安装了 Go 就可以使用,它解决了以下几个问题:
- Go 语言长久以来的依赖管理问题。
- “淘汰” 现有的 GOPATH 的使用模式。
- 统一社区中其他的依赖管理工具(提供依赖迁移功能)。
GO PATH 的弊端:
- 无版本控制概念
- 无法同步一致第三方版本号
- 无法指定当前项⽬引用的第三⽅版本号
go mod 命令
命令 | 作用 |
---|---|
go mod init | 生成 go.mod 文件 |
go mod download | 下载 go.mod 文件中指明的所有依赖 |
go mod tidy | 整理现有的依赖 |
go mod graph | 查看现有的依赖结构 |
go mod edit | 编辑 go.mod 文件 |
go mod vendor | 导出项目所有的依赖到 vendor 目录 |
got mod verify | 检验一个模块是否被篡改过 |
go mod why | 查看为什么需要依赖某模块 |
go mod 环境变量
通过 go env 命令进行查看:
$ go env
GO111MODULE="auto"
GOPROXY="https://goproxy.cn,direct"
GONOPROXY=""
GOSUMDB="sum.golang.org"
GONOSUMDB=""
GOPRIVATE=""
...
GO111MODULE
GO111MODULE 表示是否开启 Go modules 模式,允许设置以下参数:
- auto:项目包含了 go.mod 文件就启用 Go Modules。
- on:启用 Go modules ,推荐设置。
- off:禁用 Go modules,不推荐。
建议 go v1.11 后,都设置为 on:go env -w GO111MODULE=on
GOPROXY
GOPROXY 用于设置 Go 模块代理,用于使 Go 在拉取版本模块时通过镜像站点来快速拉取。
阿里云:阿里云Go Module代理服务 (aliyun.com)
七牛云:七牛云 - Goproxy.cn
建议设置为国内的地址:
go env -w GOPROXY=https://goproxy.cn,direct
direct
用于指示 Go 回源到模块版本的源地址去抓取(比如 GitHub 等)
GOSUMDB
GOSUMDB 用于检验拉取的第三方库是否完整。
默认值是 sum.golang.org(国内无法访问),但是如果设置了 GOPROXY 默认就会被代理。
GOPRIVATE
GOPRIVATE 环境变量的值也将作为 GONOPROXY 和 GONOSUMDB 的默认值。
这三个环境变量都是用于公司依赖了私有模块,需要设置,否则会拉取失败。
使用示例:
设置 git.example.com 和 github.com/aceld/zinx 是私有仓库,不会进行 GOPROXY 下载和检验
go env -w GOPRIVATE="git.example.com,github.com/aceld/zinx
设置 example.com 的子域名,比如 git.example.com、hello.example.com,都不进行 GOPROXY 下载和检验
go env -w GOPRIVATE="*.example.com"
初始化项目
使用 go mod 创建项目,不强制要求在
$GOPATH/src
目录下进行。
创建 go.mod 文件,同时为当前项目的模块命名:(以后别人通过这个名字导入你的模块)
go mod init github.com/yusael/modules_test
会生成一个 go.mod 文件:
module github.com/yusael/modules_test
go 1.17
在项目中编写源代码,如果依赖了某个库(比如:github.com/aceld/zinx/znet)
)
可以手动下载
go get github.com/aceld/zinx/znet
,也可以自动下载
下载后 go.mod 文件中会添加一行新代码:
- 含义是当前模块依赖
github.com/aceld/zinx
- 依赖的版本是
v1.0.1
// indirect
表示间接依赖
module github.com/yusael/modules_test
go 1.17
require github.com/aceld/zinx v1.0.1 // indirect
同时项目中会生成 go.sum 文件:
- go.sum 作用:列出当前项目直接或间接依赖的所有模块版本,保证今后项目依赖版本不被篡改。
h1:hash
表示对整体项目 zip 文件打开后全部文件的校验和来生成的 hash
不存在则表示依赖的库可能用不上。
xxx/go.mod h1:hash
go.mod 文件做的 hash
github.com/aceld/zinx v1.0.1 h1:WnahGyE7tDJvkJRVK2VI/m57aHEeUjr12EAYpOYW3ng=
github.com/aceld/zinx v1.0.1/go.mod h1:Tth0Fmjjpel0G8YjCz0jHdL0sXlc4p3Km/l/srvqqKo=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
修改项目版本依赖关系
go mod edit -replace=xxx@v1.0.1=xxx@v1.0.4
以上命令将 xxx@v1.0.1
的依赖修改为 xxx@v1.0.4
的依赖。
这个一般用不上,用上时请查阅当前的最新资料。
Golang 生态拓展
Web 框架
beego:github.com/astaxie/bee…
微服务框架
go kit:gokit.io/
Istio:istio.io/
容器编排
Kubernets:github.com/kubernetes/…
swarm:github.com/docker/clas…
服务发现
consul:github.com/hashicorp/c…
存储引擎
静态建站
中间件
消息队列 nsq:github.com/nsqio/nsq
TCP 长连接框架(轻量服务器):github.com/aceld/zinx
Leaf(游戏服务器):github.com/name5566/le…
RPC 框架,gRPC:grpc.io/
redis 集群:github.com/CodisLabs/c…
爬虫框架
go query:github.com/PuerkitoBio…