Golang语言基础第五部分 | 青训营

76 阅读3分钟

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 就可以使用,它解决了以下几个问题:

  1. Go 语言长久以来的依赖管理问题。
  2. “淘汰” 现有的 GOPATH 的使用模式。
  3. 统一社区中其他的依赖管理工具(提供依赖迁移功能)。

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 在拉取版本模块时通过镜像站点来快速拉取。

默认值是:proxy.golang.org,国内无法访问

阿里云:阿里云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…

gin:github.com/gin-gonic/g…

echo:github.com/labstack/ec…

lris:github.com/kataras/iri…

微服务框架

go kit:gokit.io/

Istio:istio.io/

容器编排

Kubernets:github.com/kubernetes/…

swarm:github.com/docker/clas…

服务发现

consul:github.com/hashicorp/c…

存储引擎

etcd:github.com/coreos/etcd

tidb:github.com/pingcap/tid…

静态建站

hugo:github.com/gohugoio/hu…

中间件

消息队列 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…