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

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

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 的数据的结构体。

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

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

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

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

./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 中编写这样的通用函数是否可能?

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

英文:

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:

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:

./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

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

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

func runFunctions[T1, T2 any](
    f1 func() (T1, error),
    f2 func() (T2, error),
) (res1 T1, res2 T2, err error) {
    var wg sync.WaitGroup
    wg.Add(2)

    var err1, err2 error

    go func() {
        defer wg.Done()
        res1, err1 = f1()
    }()

    go func() {
        defer wg.Done()
        res2, err2 = f2()
    }()

    wg.Wait()

    // 检查错误
    if err1 != nil {
        err = err1
        return
    }
    if err2 != nil {
        err = err2
        return
    }

    return
}

注意:

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

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

func main() {
    defer timeTrack(time.Now(), "Fetching products with sizes")
    fmt.Println("Starting synchronous calls...")

    client := &Client{}

    prodModel, sizesModel, err := runFunctions(client.GetProduct, client.GetSizes)
    if err != nil {
        // 返回错误
    }

    build(prodModel, sizesModel)
}

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

Starting synchronous calls...
product 123 has size S
product 123 has size M
product 123 has size L
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:

func runFunctions[T1, T2 any](
	f1 func() (T1, error),
	f2 func() (T2, error),
) (res1 T1, res2 T2, err error) {
	var wg sync.WaitGroup
	wg.Add(2)

	var err1, err2 error

	go func() {
		defer wg.Done()
		res1, err1 = f1()
	}()

	go func() {
		defer wg.Done()
		res2, err2 = f2()
	}()

	wg.Wait()

	// check errors
	if err1 != nil {
		err = err1
		return
	}
	if err2 != nil {
		err = err2
		return
	}

	return
}

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:

func main() {
	defer timeTrack(time.Now(), "Fetching products with sizes")
	fmt.Println("Starting synchronous calls...")

	client := &Client{}

	prodModel, sizesModel, err := runFunctions(client.GetProduct, client.GetSizes)
	if err != nil {
		// return error
	}

	build(prodModel, sizesModel)
}

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

Starting synchronous calls...
product 123 has size S
product 123 has size M
product 123 has size L
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:

确定