如何在 Golang 中更快地进行 api 调用?
How to make an api call faster in Golang?
我正在尝试使用公司 api 将一堆文件上传到他们提供的存储服务。 (基本上是我的帐户)。我有很多文件,比如 40-50 之类的。
我得到了文件的完整路径并利用 os.Open
,这样我就可以通过 io.Reader。我确实尝试过在没有 goroutines
的情况下使用 client.Files.Upload()
,但是上传它们花了很多时间并决定使用 goroutines
。这是我尝试过的实现。当我 运行 程序时,它只上传一个文件,该文件是最小的文件或等待很长时间的文件。它有什么问题?不是每次循环 运行 都会创建一个 goroutine
继续它的循环并为每个 file
创建吗?如何使用 goroutines
使其尽可能快?
var filePaths []string
var wg sync.WaitGroup
// fills the string of slice with fullpath of files.
func fill() {
filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
filePaths = append(filePaths, path)
}
if err != nil {
fmt.Println("ERROR:", err)
}
return nil
})
}
func main() {
fill()
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
client := putio.NewClient(oauthClient)
for _, path := range filePaths {
wg.Add(1)
go func() {
defer wg.Done()
f, err := os.Open(path)
if err != nil {
log.Println("err:OPEN", err)
}
upload, err := client.Files.Upload(context.TODO(), f, path, 0)
if err != nil {
log.Println("error uploading file:", err)
}
fmt.Println(upload)
}()
}
wg.Wait()
}
考虑这样的工作池模式:https://go.dev/play/p/p6SErj3L6Yc
在此示例应用程序中,我删除了 API 调用并仅列出了文件名。这使得它可以在操场上工作。
- 启动固定数量的worker goroutines。我们将使用一个渠道来分发他们的工作,我们将关闭渠道来传达工作的结束。这个数字可以是 1 个或 1000 个例程,或更多。应根据您的 putio API 可以合理预期支持的并发 API 操作数来选择数量。
paths
是我们将用于此目的的 chan string
。
- 工作人员
range
通过 paths
通道接收要上传的新文件路径
package main
import (
"fmt"
"os"
"path/filepath"
"sync"
)
func main() {
paths := make(chan string)
var wg = new(sync.WaitGroup)
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(paths, wg)
}
if err := filepath.Walk("/usr", func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("Failed to walk directory: %T %w", err, err)
}
if info.IsDir() {
return nil
}
paths <- path
return nil
}); err != nil {
panic(fmt.Errorf("failed Walk: %w", err))
}
close(paths)
wg.Wait()
}
func worker(paths <-chan string, wg *sync.WaitGroup) {
defer wg.Done()
for path := range paths {
// do upload.
fmt.Println(path)
}
}
此模式可以处理无限大的文件,而无需在处理之前将整个列表加载到内存中。如您所见,这并没有使代码更复杂——实际上,它更简单。
When I run the program it just uploads one file which is the one
函数文字继承它们被定义的范围。这就是为什么我们的代码只列出了一个路径——for 循环中的 path
变量作用域被每个 go 例程共享,所以当该变量发生变化时,所有例程都会接收到变化。
避免函数文字,除非你真的想要继承范围。在全局范围内定义的函数不会继承任何范围,您必须将所有相关变量传递给这些函数。这是一件好事 - 它使函数更易于理解,并使变量“所有权”转换更加明确。
使用函数字面量的适当情况可能是 os.Walk
参数;它的参数由 os.Walk
定义,因此定义范围是访问其他值的一种方式 - 例如 paths
通道,在我们的例子中。
说到作用域,应该避免使用全局变量,除非它们的使用范围是真正的全局变量。更喜欢在函数之间传递变量而不是共享全局变量。同样,这使变量所有权变得明确,并且可以很容易地理解哪些函数可以访问哪些变量,哪些函数不能访问哪些变量。您的等待组和 filePaths
都没有任何理由成为全球性的。
f, err := os.Open(path)
不要忘记关闭您打开的所有文件。当你处理 40 或 50 个文件时,让所有这些打开的文件句柄堆积起来直到程序结束并不是那么糟糕,但它是你程序中的一个定时炸弹,当文件数量超过 ulimit
允许打开的文件。因为函数执行大大超过了文件需要打开的部分,所以defer
在这种情况下没有意义。我会在上传文件后使用明确的 f.Close()
。
我正在尝试使用公司 api 将一堆文件上传到他们提供的存储服务。 (基本上是我的帐户)。我有很多文件,比如 40-50 之类的。
我得到了文件的完整路径并利用 os.Open
,这样我就可以通过 io.Reader。我确实尝试过在没有 goroutines
的情况下使用 client.Files.Upload()
,但是上传它们花了很多时间并决定使用 goroutines
。这是我尝试过的实现。当我 运行 程序时,它只上传一个文件,该文件是最小的文件或等待很长时间的文件。它有什么问题?不是每次循环 运行 都会创建一个 goroutine
继续它的循环并为每个 file
创建吗?如何使用 goroutines
使其尽可能快?
var filePaths []string
var wg sync.WaitGroup
// fills the string of slice with fullpath of files.
func fill() {
filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
filePaths = append(filePaths, path)
}
if err != nil {
fmt.Println("ERROR:", err)
}
return nil
})
}
func main() {
fill()
tokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
oauthClient := oauth2.NewClient(context.TODO(), tokenSource)
client := putio.NewClient(oauthClient)
for _, path := range filePaths {
wg.Add(1)
go func() {
defer wg.Done()
f, err := os.Open(path)
if err != nil {
log.Println("err:OPEN", err)
}
upload, err := client.Files.Upload(context.TODO(), f, path, 0)
if err != nil {
log.Println("error uploading file:", err)
}
fmt.Println(upload)
}()
}
wg.Wait()
}
考虑这样的工作池模式:https://go.dev/play/p/p6SErj3L6Yc
在此示例应用程序中,我删除了 API 调用并仅列出了文件名。这使得它可以在操场上工作。
- 启动固定数量的worker goroutines。我们将使用一个渠道来分发他们的工作,我们将关闭渠道来传达工作的结束。这个数字可以是 1 个或 1000 个例程,或更多。应根据您的 putio API 可以合理预期支持的并发 API 操作数来选择数量。
paths
是我们将用于此目的的chan string
。- 工作人员
range
通过paths
通道接收要上传的新文件路径
package main
import (
"fmt"
"os"
"path/filepath"
"sync"
)
func main() {
paths := make(chan string)
var wg = new(sync.WaitGroup)
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(paths, wg)
}
if err := filepath.Walk("/usr", func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("Failed to walk directory: %T %w", err, err)
}
if info.IsDir() {
return nil
}
paths <- path
return nil
}); err != nil {
panic(fmt.Errorf("failed Walk: %w", err))
}
close(paths)
wg.Wait()
}
func worker(paths <-chan string, wg *sync.WaitGroup) {
defer wg.Done()
for path := range paths {
// do upload.
fmt.Println(path)
}
}
此模式可以处理无限大的文件,而无需在处理之前将整个列表加载到内存中。如您所见,这并没有使代码更复杂——实际上,它更简单。
When I run the program it just uploads one file which is the one
函数文字继承它们被定义的范围。这就是为什么我们的代码只列出了一个路径——for 循环中的 path
变量作用域被每个 go 例程共享,所以当该变量发生变化时,所有例程都会接收到变化。
避免函数文字,除非你真的想要继承范围。在全局范围内定义的函数不会继承任何范围,您必须将所有相关变量传递给这些函数。这是一件好事 - 它使函数更易于理解,并使变量“所有权”转换更加明确。
使用函数字面量的适当情况可能是 os.Walk
参数;它的参数由 os.Walk
定义,因此定义范围是访问其他值的一种方式 - 例如 paths
通道,在我们的例子中。
说到作用域,应该避免使用全局变量,除非它们的使用范围是真正的全局变量。更喜欢在函数之间传递变量而不是共享全局变量。同样,这使变量所有权变得明确,并且可以很容易地理解哪些函数可以访问哪些变量,哪些函数不能访问哪些变量。您的等待组和 filePaths
都没有任何理由成为全球性的。
f, err := os.Open(path)
不要忘记关闭您打开的所有文件。当你处理 40 或 50 个文件时,让所有这些打开的文件句柄堆积起来直到程序结束并不是那么糟糕,但它是你程序中的一个定时炸弹,当文件数量超过 ulimit
允许打开的文件。因为函数执行大大超过了文件需要打开的部分,所以defer
在这种情况下没有意义。我会在上传文件后使用明确的 f.Close()
。