上一节: 前端程序员学习 Golang gin 框架实战笔记之一开始玩 gin
之前讲到了如何使用 gin,这一节我们来分析和调试一下它的代码。
第一行的 gin.New()
,其实还有一种写法:
gin.Default()
有什么区别呢?
你很容易查看:
你的鼠标在 New
方法停留,然后会弹出如下的窗口:
这个编辑器会告诉你这个方法的意义。
就是返回一个没有带中间件的 gin 实例。
关于中间件以后再来讲。
那么你可以点进去看 New
方法,苹果系统,按住键盘上的 command,再单击就可以进去:
进去后,可以查看它源码:
*Engine
这个是它的返回值,是个指针。
golang 也是有指针的,有点仿照 c。
关于指针,可以运行下面的地址看看:
package main
import "fmt"
func main() {
var count int = 4
fmt.Println(count)
var pv = &count
*pv = 3
fmt.Println(pv)
fmt.Println(*pv)
var pv2 *int = &count
*pv = 2
fmt.Println(pv2)
fmt.Println(*pv2)
pv3 := &count
*pv = 1
fmt.Println(pv3)
fmt.Println(*pv3)
}
我的理解就是指向值或对象的内存地址,如果要改对象或结构体内容,就传指针。
我们传结构体的时候,你不用指针,会把值传过去,可能相当于 copy 一份数据传过去,那么无法对结构体修改,只有传指针才行。
你在这个文件搜索一下 Default
方法:
我们可以比较一下,就是调了上面的 New
方法之外,多加了两个中间件:
engine.Use(Logger(), Recovery())
一个日志用的,另一个是来恢复程序用的。
我们再来看下这个 Context 是个啥。
你可以在网上找到它的概念,其他语言或框架也有,只是跟 Golang 的有些区别。
我对它的理解不是很深,先说下:
它首先是个结构体:
type Context interface {
Deadline() (deadline time.Time, ok bool)
Done() <-chan struct{}
Err() error
Value(key interface{}) interface{}
}
我看别人对它的解释是这样的:
context是一个带有截止时间,取消信号和key,value的上下文对象。
首先它有值。
到底体现在哪呢?
我们来调试一下:
先退出之前的 go run main.go
进程,用 control + c 即可。
我这里用 GoLang 编辑器:
我们用 Debug 模式来运行。
然后你用 postman 访问一下:
这个 Context 有啥值呀,就是有参数,请求这些东西,路径啥的,比如可以从 postman 得到请求的参数。
还有个例子是这样的:
package main
import (
"context"
"fmt"
"time"
)
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
go handle(ctx, 500*time.Millisecond)
select {
case <-ctx.Done():
fmt.Println("main", ctx.Err())
}
}
func handle(ctx context.Context, duration time.Duration) {
select {
case <-ctx.Done():
fmt.Println("handle", ctx.Err())
case <-time.After(duration):
fmt.Println("process request with", duration)
}
}
你执行下,看它会输出啥:
我们在写协程,或其他语言,写线程进程的时候,我们这个主协程要对这个协程的状态进行控制,不然你主协程都退出了,协程还在运行就不太好。
或者我们要共享或传一些变量,其他语言可能会用共享内存,队列,信号量啥的。
这里我们就可以传一个 Context 过去。
在 handle
协程中,我们通过 ctx.Done()
判断主程是不是执行完成,那主程要一秒后才完成,协程才 500 ms,最后肯定是先输出
fmt.Println("process request with", duration)
那 handle
方法就结束了,再过了 500 ms, 就输出:
fmt.Println("main", ctx.Err())
整个世界就结束了。
在协程中是可以知道主协的运行状态的,至少能知道是不是结束了。
你不用 context 的话,可能就像下面这样:
func main() {
fmt.Println("main is start")
go Test("value")
fmt.Println("main is finish")
time.Sleep(3 * time.Second)
}
func Test(value string) {
fmt.Println("test is start")
time.Sleep(5 * time.Second)
fmt.Println("test is finish")
}
你执行你的,我执行我的,无法获得主程和协程的状态。
这个叫“同步信号”。
我们还可以传值:
func main() {
fmt.Println("main is start")
ctx := context.WithValue(context.Background(), "key", "value")
go TestValue(ctx)
time.Sleep(1 * time.Second)
}
func TestValue(ctx context.Context) {
fmt.Println("=======:", ctx.Value("key"))
select {
case <-ctx.Done():
fmt.Println("[TestContext] and value is ", ctx.Value("key"))
}
}
使用 context.WithValue
就可以把值传给协程。
这样就共享数据了呀。
关于 Background
方法 ,可以看:
// Background returns a non-nil, empty Context. It is never canceled, has no
// values, and has no deadline. It is typically used by the main function,
// initialization, and tests, and as the top-level Context for incoming
// requests.
func Background() Context {
return background
}
// TODO returns a non-nil, empty Context. Code should use context.TODO when
// it's unclear which Context to use or it is not yet available (because the
// surrounding function has not yet been extended to accept a Context
// parameter).
func TODO() Context {
return todo
}
它返回非空的 context, 主要用于主线程来用。
网上有大佬是这样解释的: