一个常见的场景:有一个接口请求需要较长的时间(如5秒),那么,用户很可能等不及,直接就放弃了请求。

而这个接口的任务,如果在用户放弃请求后依然继续执行,那么就是浪费服务器的资源了。

所以,我们需要在得知请求中断后,主动结束耗时的任务。

context

然而这个问题的核心是如何得知http的请求是否中断。这个问题在网上居然没什么资料。。。

示例演示

taskFunc1taskFunc2
// 该方法模拟进行任务,需要1秒钟
func taskFunc1(ctx context.Context, end chan string) {
	// 监听任务执行情况的协程
	result := make(chan string)
	// 启动协程执行任务
	go func() {
		time.Sleep(1 * time.Second)
		result <- "success" // 5秒后完成任务,将结果写入信道
	}()

	// 监听 context的结束信号和任务结束的信号,哪个先到就先执行哪个
	select {
	case <-ctx.Done():
		end <- "taskFunc1失败"
	case <-result:
		end <- "taskFunc1成功"

	}
}

// 该方法模拟进行任务,需要5秒钟,和上面是一样的
func taskFunc2(ctx context.Context, end chan string) {
	result := make(chan string)
	go func() {
		time.Sleep(5 * time.Second)
		result <- "success"
	}()

	select {
	case <-ctx.Done():
		end <- "taskFunc2失败"
	case <-result:
		end <- "taskFunc2成功"
	}
}

他们内容都是一样的,都有两个参数:

ctx context.Contexthttp请求上文任务下文end chan string

接下来实现一个接口:

// 1.一个测试接口
func Test1(c *gin.Context) {

	ctx := c.Request.Context() // 获取http请求的上下文对象

	// 主线程判断两个协程是否运行结束的信道
	end1 := make(chan string)
	end2 := make(chan string)
	// 启动两个协程分别完成任务
	go taskFunc1(ctx, end1)
	go taskFunc2(ctx, end2)
	// 监听两个协程是否结束
	err2, err1 := <-end2, <-end1
	fmt.Println(err1, err2)
}

taskFunc2
taskFunc2taskFunc2

gin.Context

gin.ContextContext
context
gin.Context.Request.Context()

适用场景

contextgin.Context
mongo-drivergo-rediscontext.Context
context.TODO()context
context

于是现在也就知道了mongodb和redis要求传入这个参数的意义了:就是为了让我们能够主动结束它的任务,而不至于浪费资源继续请求数据,毕竟数据库资源是宝贵的。

当然,我们有必要每次都将gin的Context传入这些api吗?其实没有必要,因为大多数对数据库资源的请求也就是那么几十毫秒,为了节省这点资源,我们的代码需要多写很多,个人认为不需要做这种意义不大的事。

所以,我们还是针对一些特定的场景才使用,比如某些任务需要很长的时间等等。

context.TODO()

那么,为什么gorm对数据库的操作又不需要这个参数呢?还没了解过,以后再说