在 Go 中我无法弄清楚如何获得以下设置。
- 第一级 goroutine 应该在终止之前停止其第二级 goroutine。
- 如果函数
runGoroutine
没有循环(我不希望它循环),那么当消息发送到通道时,goroutine 将如何退出?如果没有循环,那么case <-stopChan:
只会在开始时被调用一次。 - 如果函数
runGoroutine
调用其他异步函数会怎么样?当返回时这些函数也会退出吗?
也许我的方法不对。在我的情况下,我有x
用户可以启动的任务组,当用户启动任务组时,会创建一个 goroutine(第 1 级)以返回 CL 用户访问权限。此任务组(第 1 级)是y
API 请求的集合(实际上 y 是 200-300)。对于每个 API 请求,我都会创建一个新的 go-routine(第 2 级)。我的目标是速度;如果我每秒执行数百个 API 请求,将每个请求放入其自己的 goroutine 中是否更快/更有效?或者在 1 个线程上执行是否相同?
Parent/Main process
| |
Child goroutine Child goroutine
| |
X bottom goroutines X bottom goroutines (API level)
package main
import (
"fmt"
"time"
)
func runGoroutine(stopChan <-chan struct{}) {
for {
select {
case <-stopChan:
return // Exit the goroutine
default:
fmt.Println("Goroutine is working...")
time.Sleep(1 * time.Second)
}
}
}
func main() {
stopChan := make(chan struct{})
// Start the goroutine
go runGoroutine(stopChan)
// User should be able to close the routines they want
close(stopChan)
}
新代码
package main
import (
"context"
"log"
"net/http"
"sync"
"time"
)
func main() {
// Initialize context with cancellation
ctx, cancel := context.WithCancel(context.Background())
// Call the worker pool in a separate goroutine to allow main to return immediately
// We can start any task group like this
go startWorkerPool(ctx, 20, 100)
// User has CL access on main thread
// Main function returns, but `cancel` is accessible to allow later cancellation if needed
log.Println("Worker pool started; you can cancel it by calling cancel()")
waitForNine()
}
func waitForNine() {
var input int
for input != 9 {
fmt.Print("Enter a number: ")
fmt.Scan(&input)
}
}
// startWorkerPool starts a fixed-size worker pool to handle `numRequests` requests.
func startWorkerPool(ctx context.Context, numWorkers, numRequests int) {
var wg sync.WaitGroup
work := make(chan string)
// Launch the specified number of workers
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func(ctx context.Context) {
defer wg.Done()
client := http.Client{}
for url := range work {
if req, err := http.NewRequestWithContext(ctx, "GET", url, nil); err != nil {
log.Println("failed to create new request:", err)
} else if resp, err := client.Do(req); err != nil {
log.Println("failed to make request:", err)
} else {
resp.Body.Close()
}
}
}(ctx)
}
// Enqueue URLs for workers and close the channel when done
go func() {
for i := 0; i < numRequests; i++ {
work <- "https://httpbin.org/get"
}
close(work)
}()
// Wait for all workers to finish
wg.Wait()
log.Println("All requests processed")
}
共享
context
是一次性取消多个 goroutine 的简单方法。考虑这个简单的例子:此代码启动 100 个并发 goroutine 来请求
https://httpbin.org/get
一个方便的测试 URL。程序启动一秒后,它们的共享上下文被取消,导致 http 请求中断。您可以使用信号或其他事件来取消共享上下文。启动 goroutine确实比通过通道传递值有更多开销。启动 http goroutine 的工作池并将工作分配给它们很有意义。
下一个示例创建了 20 个 goroutine,这 20 个 goroutine 处理所有 100 个请求。当工作负载非常高时,工作池模式更为实用。
更新
现在我们已经有了工作池,让我们让它从标准输入中拉取我们的 URL 列表。用户可以通过键入 URL 来提供 URL,或者通过将文件内容或其他来源重定向到标准输入来提供 URL。
我们要做的就是开始从标准输入读取我们的 URL,并将它们传递到我们的工作队列以便在后台检索。
我们一次只能处理这么多工作。过了这个点,我们可以选择让主机超负荷运转,或者暂停处理新工作;一般来说,后者更可取。在这个实现中,我们通过为 提供有限的缓冲区大小来限制我们排队的工作量
work
。您可以以交互方式运行程序,也可以从文件或其他来源提供输入。以下是在命令行提供 3 个 URL 并立即取消的示例。
如果省略
cancel
,程序仍会在处理完 URL 列表后结束,然后程序完成。这是因为scanner
到达了文件末尾。您可以在命令行中使用 Ctrl-D 发出文件末尾信号。