如何编写Go泛型函数并发执行两个返回结构体的客户端方法?
场景描述
我在编写Go包时,经常需要发起两次独立的HTTP请求获取数据。包内的客户端方法统一为无参数、返回结构体指针+error的形式,例如:
func (c Client) GetProduct() (*Product, error) func (c Client) GetSizes() (*Sizes, error)
我希望实现一个泛型函数,接收两个这类方法作为参数,并发执行它们,最终返回填充好数据的两个结构体。理想的调用方式如下:
product, sizes, err := runFunctions(client.GetProduct, client.GetSizes)
当前问题
我尝试编写了一个泛型函数,但编译时出现类型不匹配错误:
./prog.go:54:28: cannot use client.GetProduct (value of type func() (*Product, error)) as type func() (interface{}, error) in argument to runFunctions
./prog.go:54:47: cannot use client.GetSizes (value of type func() (*Sizes, error)) as type func() (interface{}, error) in argument to runFunctions
这是因为Go的函数类型是严格匹配的,func() (*Product, error)与func() (interface{}, error)属于不同类型,无法直接转换。
解决方案:适配泛型类型参数
要解决这个问题,我们需要让泛型函数支持两种不同的返回类型,通过定义两个独立的类型参数来适配不同的客户端方法。以下是修正后的泛型函数实现:
func runFunctions[T1, T2 any](f1 func() (*T1, error), f2 func() (*T2, error)) (*T1, *T2, error) { errChan := make(chan error, 2) var res1 *T1 var res2 *T2 // 并发执行第一个函数 go func() { var err error res1, err = f1() errChan <- err }() // 并发执行第二个函数 go func() { var err error res2, err = f2() errChan <- err }() // 收集错误 var err error for i := 0; i < 2; i++ { if e := <-errChan; e != nil { err = e // 可选:如果需要遇到第一个错误就返回,可以在这里关闭chan并退出,但要注意goroutine泄漏 // close(errChan) // return res1, res2, err } } close(errChan) return res1, res2, err }
这个实现的核心是通过T1和T2两个泛型类型参数,分别匹配两个客户端方法返回的结构体类型,从而避免了类型不匹配的问题。
并发使用的通用建议
灵活的错误处理:
上述例子收集所有错误,最终返回最后一个非nil错误;如果需要优先返回第一个错误,可以在收到第一个错误后关闭错误通道并立即返回,但要确保剩余的goroutine不会泄漏(比如给客户端方法添加context支持,通过context取消未完成的请求)。如果需要区分是哪个请求出错,可以返回[]error或者自定义包含请求标识的错误类型。上下文与取消:
建议给客户端方法添加context.Context参数,这样在runFunctions中可以传入带超时/取消的context,当需要终止请求时能及时释放资源,避免goroutine泄漏。示例改造:客户端方法改为func (c Client) GetProduct(ctx context.Context) (*Product, error),泛型函数也相应添加context参数。并发数控制:
如果后续需要并发执行更多请求,不要无限制创建goroutine,建议使用worker pool模式控制并发数量,避免耗尽系统资源。竞态安全:
确保每个结果变量只被一个goroutine写入,避免数据竞态。上述代码中res1和res2分别由对应的goroutine单独赋值,不存在竞态问题,无需加锁。结果有效性:
如果某个请求出错,对应的结果结构体可能为nil,调用方需要在处理结果前先检查错误,避免空指针panic。
内容的提问来源于stack exchange,提问作者slatermorgan

