这是我参与「第五届青训营 」笔记创作活动的第2天。
引言
今天的课程涉及了并发编程(利用Go协程,信道和锁)、Go项目依赖管理、测试和一个服务端项目实战(使用Gin框架),这篇文章主要是针对今天课程的记录和总结。
一、本堂课重点内容
本堂课的知识点
- 协程和信道
- 项目依赖 Go Module
- 测试:单元测试,Mock测试和基准测试
- 项目实践:利用分层架构,文件读取,Gin(高性能web框架)
二、详细知识点介绍
1. 并发编程
协程
Go 协程是与其他函数或方法一起并发运行的函数或方法。Go 协程可以看作是轻量级线程。与线程相比,创建一个 Go 协程的成本很小。因此在 Go 应用中,常常会看到有数以千计的 Go 协程并发地运行。 协程位于用户态,线程位于核心态(需要内核直接进行操作,用户无权限)。 举例:
package main
import (
"fmt"
"time"
)
func hello() {
fmt.Println("Hello world goroutine")
}
func main() {
go hello()
time.Sleep(1 * time.Second) //防止主协程直接结束,导致上面开启的协程还未执行就已经退出程序了
fmt.Println("main function")
}
信道
利用信道来实现协程之间的信息交换,互相配合的并发执行
信道可以想象成 Go 协程之间通信的管道。如同管道中的水会从一端流到另一端,通过使用信道,数据也可以从一端发送,在另一端接收。 举例:
package main
import "fmt"
func main() {
var a chan int //声明
if a == nil {
fmt.Println("channel a is nil, going to define it")
a = make(chan int)
fmt.Printf("Type of a is %T", a)
}
//简短定义
b := make(chan int)
data:="dddd"
data := <- b // 读取信道 b, 将信道中的数据存入data变量中
b <- data // 写入信道 b, 将数据data 写入信道b中
}
2. 依赖管理
1️⃣在以前版本,Go所依赖的所有的第三方库都放在GOPATH这个目录下面,而这会导致了同一个库只能保存一个版本的代码。当不同的项目依赖同一个第三方的库的不同版本时,就会有问题
2️⃣Go语言从v1.5开始开始引入vendor模式,如果项目目录下有vendor目录,那么go工具链会优先使用vendor内的包进行编译、测试等。
vender文件夹下是项目依赖的包的源代码文件。
vender机制
Go1.5版本之后开始支持能够控制Go语言程序编译时依赖包搜索路径的优先级。
当查找项目的某个依赖包,首先会在项目根目录下的vender文件夹中查找,如果没有找到就会去$GOAPTH/src目录下查找。
3️⃣go module是Go1.11版本之后官方推出的版本管理工具,并且从Go1.13版本开始,go module将是Go语言默认的依赖管理工具。
使用 go module 管理依赖后会在项目根目录下生成两个文件go.mod和go.sum。
go.mod文件记录了项目的依赖信息,go.sum文件记录每个依赖库的版本和哈希值。
当启用go module管理项目,就会忽略go path和vender文件夹,会根据go.mod文件去下载项目所需依赖
3. 实战-利用Gin
分层架构
整体架构分为三层,repository数据层(如从文件中获取数据,从持久化数据库中获取数据),service逻辑层(处理业务功能的核心逻辑),controller视图层(封装数据(如JSON格式),传输给客户端)
Gin
Gin是由Golang编写的高性能Web框架
基本使用:
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
//定义路由的GET方法及响应处理函数
r.GET("/hello", func(c *gin.Context) {
//将发送的信息封装成JSON发送给浏览器
c.JSON(http.StatusOK, gin.H{
//这是我们定义的数据
"message": "Gin 测试使用",
})
})
r.Run() //默认在本地8080端口启动服务
}
路由
路由方法有 GET, POST, PUT, PATCH, DELETE 和 OPTIONS,还有Any,可匹配以上任意类型的请求。
//URL相同,但路由方式不同
r.GET("/login", func(c *gin.Context) {...})
r.POST("/login", func(c *gin.Context) {...})
//Any 可以匹配任意类型
r.Any("/test", func(c *gin.Context) {...})
路由组
func main() {
r := gin.Default()
userGroup := r.Group("/user")
{
userGroup.GET("/index", func(c *gin.Context) {...})
userGroup.GET("/login", func(c *gin.Context) {...})
userGroup.POST("/login", func(c *gin.Context) {...})
}
shopGroup := r.Group("/shop")
{
shopGroup.GET("/index", func(c *gin.Context) {...})
shopGroup.GET("/cart", func(c *gin.Context) {...})
shopGroup.POST("/checkout", func(c *gin.Context) {...})
}
r.Run()
}
三、实践练习例子
协程结合信道
package main
import (
"fmt"
)
func hello(done chan bool) {
fmt.Println("Hello world goroutine")
done <- true
}
func main() {
done := make(chan bool)
go hello(done)
re := <-done //通过信道 done 接收数据。这一行代码发生了阻塞,除非有协程向 done 写入数据,否则程序不会跳到下一行代码
fmt.Println(re) //输出true
fmt.Println("main function")
}
从上面这个例子可知,利用信道,可做到并发的协程之间的合作,如:若一个协程A到达某一个步骤需要另外一个协程B的数据时,可利用信道来达到阻塞A协程,直到B协程利用信道将数据发送给A(且A收到) 反过来,当我们一个协程利用信道传输数据,而没有其他的Go协程去接收数据,那么其自己也会被阻塞,乃至死锁
当然除了上面的双向信道,还有单向信道
package main
import "fmt"
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
sendch := make(chan<- int) //唯送信道,不能接收数据
go sendData(sendch)
fmt.Println(<-sendch) //报错,因为不能接收
}
四、课后个人总结
今天这堂课程懂得了如何利用Go协程和信道进行并发编程,Go不愧是为并发而生的。然后懂得了配置Go项目依赖等步骤,之后学习了一个很重要的过程就是测试,在项目上线之前,测试十分的重要,然后学习了web开发的一些流程和开发模式,最后学习使用了Gin框架。
五、引用参考
主要参考了青训营课程资料和Go语言中文网中的相关资料