之前讲到了如何使用 gin,这一节我们来分析和调试一下它的代码。

New()

gin.New()
gin.Default()

有什么区别呢?

你很容易查看:

New

这个编辑器会告诉你这个方法的意义。

就是返回一个没有带中间件的 gin 实例。

关于中间件以后再来讲。

New

进去后,可以查看它源码:

*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

我们再来看下这个 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

我这里用 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 过去。

handlectx.Done()
fmt.Println("process request with", duration)
handle
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, 主要用于主线程来用。

网上有大佬是这样解释的: