你可以编写一个函数,接受两个返回结构体的函数,并同时运行它们。

huangapple go评论106阅读模式
英文:

How can I write a function which accept 2 functions (which return structs) and runs them concurrently?

问题

在我正在编写的一个 golang 包中,我经常需要进行两个 HTTP 请求来获取所需的数据。

我的包包含客户端函数,通常没有参数,并返回一个结构体和一个错误。

func (c Client) GetProduct() (*Product, error)

然而,我希望能够编写一个通用函数,它可以接受这两个客户端函数作为参数,同时运行它们,并简单地返回填充有来自我所访问的 API 的数据的结构体。

理想情况下,我希望函数调用看起来像这样:

  1. struct1, struct2, err := runFunctions(client.GetProduct, client.GetSizes)

到目前为止,我已经编写了以下通用函数:https://go.dev/play/p/IA8LJqY0FPe

问题是,因为 GetProductGetSizes 都返回一个 structerror,而不是一个 interface{}error,所以在编译时我会得到以下错误:

  1. ./prog.go:54:28: cannot use client.GetProduct (value of type func() (*Product, error)) as type func() (interface{}, error) in argument to runFunctions
  2. ./prog.go:54:47: cannot use client.GetSizes (value of type func() (*Sizes, error)) as type func() (interface{}, error) in argument to runFunctions

我的问题是如何解决这个问题?在 go 中编写这样的通用函数是否可能?

同时,如果有关于如何使用并发的一般提示,我也会很感激。

英文:

Within a golang package I am writing, I often have to make 2 HTTP requests to get the data I require.

My package contains client functions, which usually have 0 arguments and return a struct and an error.

func (c Client) GetProduct() (*Product, error)

However, I would like to be able to write a generic function, which could accept two of these client functions as arguments, run them concurrently and simply return the structs filled with data from the API I am hitting.

Ideally, I would like the function call to look like this:

  1. struct1, struct2, err := rrunFunctions(client.GetProduct, client.GetSizes)

So far I have written the following generic function: https://go.dev/play/p/IA8LJqY0FPe

The problem is that because GetProduct and GetSizes both return a struct and error rather than an interface{} and error I get the following error at compile time:

  1. ./prog.go:54:28: cannot use client.GetProduct (value of type func() (*Product, error)) as type func() (interface{}, error) in argument to runFunctions
  2. ./prog.go:54:47: cannot use client.GetSizes (value of type func() (*Sizes, error)) as type func() (interface{}, error) in argument to runFunctions

My question is how do I get past this? Is writing a function generic as this possible in go?

Any general tips on using concurrency in this manner would also be appreciated.

答案1

得分: 3

是的,使用泛型是可以实现的。在函数的返回类型中使用两个类型参数:

  1. func runFunctions[T1, T2 any](
  2. f1 func() (T1, error),
  3. f2 func() (T2, error),
  4. ) (res1 T1, res2 T2, err error) {
  5. var wg sync.WaitGroup
  6. wg.Add(2)
  7. var err1, err2 error
  8. go func() {
  9. defer wg.Done()
  10. res1, err1 = f1()
  11. }()
  12. go func() {
  13. defer wg.Done()
  14. res2, err2 = f2()
  15. }()
  16. wg.Wait()
  17. // 检查错误
  18. if err1 != nil {
  19. err = err1
  20. return
  21. }
  22. if err2 != nil {
  23. err = err2
  24. return
  25. }
  26. return
  27. }

注意:

  • 使用defer调用wg.Done()
  • 必须在wg.Wait()之后检查错误!

使用它也变得更简单,因为你不必处理类型断言:

  1. func main() {
  2. defer timeTrack(time.Now(), "Fetching products with sizes")
  3. fmt.Println("Starting synchronous calls...")
  4. client := &Client{}
  5. prodModel, sizesModel, err := runFunctions(client.GetProduct, client.GetSizes)
  6. if err != nil {
  7. // 返回错误
  8. }
  9. build(prodModel, sizesModel)
  10. }

这将输出(在Go Playground上尝试):

  1. Starting synchronous calls...
  2. product 123 has size S
  3. product 123 has size M
  4. product 123 has size L
  5. 2009/11/10 23:00:05 Fetching products with sizes took 5s
英文:

Yes, this is possible using generics. Use 2 type parameters for the 2 return types of the functions:

  1. func runFunctions[T1, T2 any](
  2. f1 func() (T1, error),
  3. f2 func() (T2, error),
  4. ) (res1 T1, res2 T2, err error) {
  5. var wg sync.WaitGroup
  6. wg.Add(2)
  7. var err1, err2 error
  8. go func() {
  9. defer wg.Done()
  10. res1, err1 = f1()
  11. }()
  12. go func() {
  13. defer wg.Done()
  14. res2, err2 = f2()
  15. }()
  16. wg.Wait()
  17. // check errors
  18. if err1 != nil {
  19. err = err1
  20. return
  21. }
  22. if err2 != nil {
  23. err = err2
  24. return
  25. }
  26. return
  27. }

Notes:

  • Use defer to call wg.Done()
  • You must check errors after wg.Wait()!

Using it also becomes simpler as you don't have to deal with type assertions:

  1. func main() {
  2. defer timeTrack(time.Now(), "Fetching products with sizes")
  3. fmt.Println("Starting synchronous calls...")
  4. client := &Client{}
  5. prodModel, sizesModel, err := runFunctions(client.GetProduct, client.GetSizes)
  6. if err != nil {
  7. // return error
  8. }
  9. build(prodModel, sizesModel)
  10. }

This will output (try it on the Go Playground):

  1. Starting synchronous calls...
  2. product 123 has size S
  3. product 123 has size M
  4. product 123 has size L
  5. 2009/11/10 23:00:05 Fetching products with sizes took 5s

huangapple
  • 本文由 发表于 2023年1月5日 17:29:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/75016290.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定