一般 context 声明放在哪里合适

2022-11-13 20:49:20 +08:00
 seers

有一个函数,要并发访问好几个数据库,返回时间不一样,就叫做 longTimeTask()吧,我想用 context 设置超时,现在试下了,context 声明放在 main 里面,就所有 goroutine 共享了,不符合我要求,如果这样是可以的:

	go func() {
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()
		longTimeTask(ctx)
	}()

后面我发现这样也行:

func longTimeTask(){
		ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
		defer cancel()
        }

所以想问问哪种最合适

2822 次点击
所在节点    Go 编程语言
8 条回复
pennai
2022-11-14 00:22:22 +08:00
如果我 CR 的话会觉得第一种好一点,没有那么隐式,因为长任务与超时没有必然联系
CEBBCAT
2022-11-14 02:00:31 +08:00
没有明显区别,要改的话用 IDE 重构功能改也不是特别麻烦。风格这种事,场景多了之后容易分析。


我看其实只有两点区别,传不传入底层 context ,以及谁来控制 3s 的这个 threshold
securityCoding
2022-11-14 03:11:29 +08:00
用入参显示声明就好
tikazyq
2022-11-14 09:39:35 +08:00
从工程化的角度来看,尽量不要引入 implicit variable ,这会增加耦合性和复杂性。如果上下文 context 跟当前函数是高内聚,天然耦合性强的,就直接定义为私有变量,没问题,也就是第二种;但如果 context 会在其他地方被引用,或需要控制,就必须放在外层,作为参数被调用,或者用函数传入,也就是第一种。

总的来看,要视情况而定,单从代码量来看,第二种 4 行代码肯定优于第一种 5 行代码 🐶
ScepterZ
2022-11-14 09:46:54 +08:00
一般服务里是一个请求一个 context ,一般框架会直接给你准备好,你一路传下去就行,如果框架没有的话就自己在处理一开始的时候新建一个
Akiya
2022-11-14 10:59:44 +08:00
这个跟 context 没有关系,还是看业务,如果 longTimeTask 是需要外部来控制 timeout 就是第一种,如果完全不需要外部控制就是第二种
RedisMasterNode
2022-11-14 11:28:32 +08:00
第一种合适,第一种的含义是在主 goroutine 中指定所有派生的 goroutine 都必须在(主 goroutine 视角) 3 秒内做完;第二种意思是主 goroutine 派生一堆 goroutine ,不管多久能做完;但是每个派生 goroutine 单独控制超时 3 秒。

我觉得虽然最终实现出来的效果可能没什么差别,但是从操作语义上觉得还是主 goroutine 统一管控比较好。

最后写出来的效果应该像这样(随手写的,很可能跑不通,但是琢磨应该大致结构大差不差):
- [https://go.dev/play/p/A1ae5LNzsQu]( https://go.dev/play/p/A1ae5LNzsQu)

```go
package main

import "fmt"

func main() {

ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()

wg := sync.WaitGroup{}
for i:= 0; i < 10; i++ {
wg.Add(1)
go longTimeTask(ctx context.Context) {
// some DB query here
wg.Done()
return
}(ctx)
}

wg.Wait()
}}

```
akaHenry
2022-11-15 10:08:56 +08:00
6L 正解.

以上其他 L, 扯淡.

鉴于提问在 go 标签下, go 的 Context, 主要有 2 种用法.

1. 用于替代全局变量, 更安全的透传"偏全局的"参数. 常用于: web 的 http Ctx, 携带 http 请求参数, 并在透传中, 注入新的参数, 向下传.
2. 并发控制. 更优雅的控制 Goroutine 退出. 常用于: db/redis/mq/rpc 等中间件 client 的退出管理.

多看一些 web framework 源码, 在 graceful shutdown 处, 都可看到 context 的典型用法.


其他语言, python 的 django http request 的源码, 也有类似设计.

Context, 是一种设计范式. 至于是要在 main 全局定义, 还是局部定义, 是具体业务场景决定的. 具体问题, 具体分析.

我给出的 2 种用法, 就存在 main 全局定义的 ctx, 也存在定义在局部的 ctx.



PS:

不懂, 就不要强答, 误人子弟.

写代码, 不是八股文. 要搞清楚本质.

错的答案, 比不回答. 更糟糕.

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/894941

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX