V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
seers
V2EX  ›  Go 编程语言

一般 context 声明放在哪里合适

  •  
  •   seers · 2022-11-13 20:49:20 +08:00 · 2801 次点击
    这是一个创建于 739 天前的主题,其中的信息可能已经有所发展或是发生改变。

    有一个函数,要并发访问好几个数据库,返回时间不一样,就叫做 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()
            }
    

    所以想问问哪种最合适

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


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

    总的来看,要视情况而定,单从代码量来看,第二种 4 行代码肯定优于第一种 5 行代码 🐶
    ScepterZ
        5
    ScepterZ  
       2022-11-14 09:46:54 +08:00
    一般服务里是一个请求一个 context ,一般框架会直接给你准备好,你一路传下去就行,如果框架没有的话就自己在处理一开始的时候新建一个
    Akiya
        6
    Akiya  
       2022-11-14 10:59:44 +08:00
    这个跟 context 没有关系,还是看业务,如果 longTimeTask 是需要外部来控制 timeout 就是第一种,如果完全不需要外部控制就是第二种
    RedisMasterNode
        7
    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
        8
    akaHenry  
       2022-11-15 10:08:56 +08:00   ❤️ 1
    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:

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

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

    错的答案, 比不回答. 更糟糕.
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3907 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 05:20 · PVG 13:20 · LAX 21:20 · JFK 00:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.