并发编程与测试
sync用于不需要通信的情况
var wg sync.WaitGroup
func download(url string) {
fmt.Println("start to download", url)
time.Sleep(time.Second) // 模拟耗时操作
wg.Done()
}
for i := 0; i < 3; i++ {
wg.Add(1)
go download("a.com/" + string(i+'0'))
}
wg.Wait()
fmt.Println("Done!")
- wg.Add(1):为 wg 添加一个计数,wg.Done(),减去一个计数。
- go download():启动新的协程并发执行 download 函数。
- wg.Wait():等待所有的协程执行结束。
如果需要通信我们需要使用channel 信道
make(chan string, 10)
创建大小为 10 的缓冲信道
var ch = make(chan string, 10) // 创建大小为 10 的缓冲信道
func download(url string) {
fmt.Println("start to download", url)
time.Sleep(time.Second)
ch <- url // 将 url 发送给信道
}
for i := 0; i < 3; i++ {
go download("a.com/" + string(i+'0'))
}
for i := 0; i < 3; i++ {
msg := <-ch // 等待信道返回消息。
fmt.Println("finish", msg)
}
fmt.Println("Done!")
// calc.go
package main
func add(num1 int, num2 int) int {
return num1 + num2
}
// calc_test.go
package main
import "testing"
func TestAdd(t *testing.T) {
if ans := add(1, 2); ans != 3 {
t.Error("add(1, 2) should be equal to 3")
}
}
运行 go test
,将自动运行当前 package 下的所有测试用例,如果需要查看详细的信息,可以添加-v
参数。
go test --cover
命令可以查看测试的覆盖率
运行测试想要当测试文件所在的文件夹运行go test
PS E:\0Learn_to_code\Go\src\study> go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok study 0.109s
错误处理
defer会在函数退出时执行放入的命令,一个函数有多个defer时采用逆序的方式执行,也就是先执行最后定义的
func main() {
defer func() {
fmt.Println("defer func")
}()
arr := []int{1, 2, 3}
fmt.Println(arr[4])
}
recover可以捕获panic防止程序异常退出
func test_recover() {
defer func() {
fmt.Println("defer func")
if err := recover(); err != nil {
fmt.Println("recover success")
}
}()
arr := []int{1, 2, 3}
fmt.Println(arr[4])
fmt.Println("after panic")
}
func main() {
test_recover()
fmt.Println("after recover")
}
包与模块
一般来说,一个文件夹可以作为 package,同一个 package 内部变量、类型、方法等定义可以相互看到。比如我们新建一个文件 calc.go
, main.go
平级,分别定义 add 和 main 方法。就算add是小写的main也可也访问因为他们都是main包的
Go 语言也有 Public 和 Private 的概念,粒度是包。如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见。
包名最好和文件夹的名字是一样的
性能建议
- 使用make时尽可能预先分配内存,防止频繁的分配内存
- 使用已有切片创建新切片时,会导致已有切片一直处于被应用状态,无法被释放尽量使用cope来实现
- 对字符串操作时尽可能使用strings.Builder,因为在go中字符串是常量频繁的操作可以导致频繁分配内存,strings.Builder可以避免
- go中空结构体struct{}是不占用空间的
- 使用原子变量比加锁效率高,因为锁是通过操作系统实现的,而原子是通过硬件实现的
Gin
//使用了gin.Default()生成了一个实例,这个实例即 WSGI 应用程序。
r := gin.Default()
//声明一个路由
r.GET("/", func(c *gin.Context) {
c.String(200, "Hello, Geektutu")
})
r.Run("0.0.0.0:1234") // 启动服务器,监听 0.0.0.0:1234
http的理由分为很多种
///user/gaslf进入这个
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(200, "Hello, "+name)
})
//无参 /user进入这个
r.GET("/user", func(c *gin.Context) {
c.String(200, "Hello, 无名之人")
})
///users?name=Tom&role=student带有参数的
r.GET("/users", func(c *gin.Context) {
name := c.Query("name") //查找name没有就返回空字符串
role := c.DefaultQuery("role", "teacher") //查找role没有就返回默认值"teacher"
c.String(200, "Hello, "+name+"! You are a "+role)
})
重定向
r.GET("/redirect", func(c *gin.Context) {
//当浏览器访问/redirect,浏览器会被重定向到`/index`,并且状态码为301,表示这是一永久的重定向
c.Redirect(http.StatusMovedPermanently, "/index")
})
r.GET("/goindex", func(c *gin.Context) {
//当访问/goindex时会清空url再重新访问一次gin,这个性能比上面的消耗高
c.Request.URL.Path = "/"
r.HandleContext(c)
})
分组路由
// group routes 分组路由
defaultHandler := func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"path": c.FullPath(),
})
}
//创建一个名为`v1`的路由分组,所有属于这个分组的路由都会以`/v1`为前缀。
v1 := r.Group("/v1")
{//这个花括号没有实际性的意义只是为了好看
///v1/posts
v1.GET("/posts", defaultHandler)
v1.GET("/series", defaultHandler)
}
// group: v2
v2 := r.Group("/v2")
{
v2.GET("/posts", defaultHandler)
v2.GET("/series", defaultHandler)
}
自定义的中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// 给Context实例设置一个值
c.Set("geektutu", "1111")
// 请求前
c.Next()//调用这个后将会等待请求处理完毕后再执行下面的
// 请求后
latency := time.Since(t)
log.Print("哈哈哈哈我是中间件", latency)
}
}
r.Use(Logger()) //全局的使用中间件
// 作用于单个路由
r.GET("/benchmark", Logger(), benchEndpoint)
// 作用于某个组
authorized := r.Group("/")
authorized.Use(Logger())
{
authorized.POST("/login", loginEndpoint)
authorized.POST("/submit", submitEndpoint)
}
微服务框架
用户发起请求,先由负载均衡接收后给一个api网关,网关再使用rpc协议将数据给到响应的服务上
- 服务注册 服务将自己注册到服务中心里
- 服务发现 找到已经注册的服务
- 服务发布 让一个服务升级运行新的代码
随机数的运用与理解
猜数字游戏往往需要生成一个随机的谜底数字。Go 语言中的math/rand
包提供了生成随机数的功能,但要注意正确地初始化随机数生成器。这让我们深入理解了伪随机数的概念以及如何在程序中运用它们。例如,需要使用当前时间作为种子来初始化rand
,以确保每次游戏开始时谜底数字都是不同的,增加游戏的可重复性和可玩性。
错误处理的必要性
在玩家输入猜测的过程中,可能会出现各种意外情况,如输入格式错误、超出范围等。妥善的错误处理可以保证程序的稳定性。使用defer
和recover
机制或者在输入读取阶段进行严格的格式检查,可以避免程序因用户的错误操作而崩溃。这让我们明白在任何程序中,都不能忽视对可能出现的错误情况的处理,就像为程序穿上了一层坚固的 “铠甲”,使其能在各种复杂的环境下正常运行。