为什么我的go代码在使用goroutine和通道时挂起



所以我想向一个api端点发出多个http请求,我现在需要支付15000英镑。我想尽可能快地发出这些请求,但同时也不会遇到任何操作系统问题,因为当我运行它时,只需为我在套接字中遇到的每个请求创建一个新的goroutine:打开的文件太多错误。因此,经过一些研究,我想出了使用缓冲通道来堆叠需要运行的作业,下面是我得到的代码。

我的问题是,它将要运行的1000个作业添加到通道中,但实际上并没有运行它们——它只是挂起,在我终止程序之前什么都不做。为什么会这样?

package main
import (
"fmt"
"sync"
)
func main() {
jobsChannel := make(chan string, 1000)
workers := 10
var wg sync.WaitGroup
usernameAsPointer := getCommandArgs()
username := *usernameAsPointer
usernameID := getID(username)
listOfAccounts := readFile()
fmt.Printf("Loaded %d accounts!n", len(listOfAccounts))
// Fill up our jobs channel
for _, account := range listOfAccounts {
fmt.Println("Adding Jobs to the channel")
fmt.Println(account)
wg.Add(1)
jobsChannel <- account
}
// Create a worker pool so we dont just kill the system we're running this on
for i := 0; i < workers; i++ {
fmt.Println("We're running our jobs.")
go func() {
for accountInfo := range jobsChannel {
followUser(usernameID, accountInfo)
wg.Done()
}
}()
}
wg.Wait()
}

因为程序在工作者启动之前填充通道,所以当len(listOfAccounts)大于cap(jobsChannel)时,程序将挂起。先启动工人来解决问题。

另一个问题是工人goroutines将永远挂在jobsChannel上接收。关闭通道以完成这些goroutines。

使用等待组来等待goroutine,而不是作业。

有了这些更改,任何容量都适用于jobsChannel。一些样式指南建议使用容量0或1,除非有特定原因需要使用更大的容量。没有理由使用1000的容量,所以我将容量切换为1。

这是带有附加注释的更新代码。

jobsChannel := make(chan string, 1)
workers := 10
var wg sync.WaitGroup
usernameAsPointer := getCommandArgs()
username := *usernameAsPointer
usernameID := getID(username)
listOfAccounts := readFile()
// Create the worker goroutines. Increment wait group when
// creating a worker and decrement when the worker exits.
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for accountInfo := range jobsChannel {
followUser(usernameID, accountInfo)
}
}()
}
// Send jobs to the workers.
for _, account := range listOfAccounts {
fmt.Println("Adding Jobs to the channel")
fmt.Println(account)
jobsChannel <- account
}
// Close channel to signal workers that no more jobs are coming.
// The workers break out of the for range loops when the channel is
// closed.
close(jobsChannel)
// Wait for workers to complete.
wg.Wait()

使用通道和WaitGroups的方式很脆弱。频道不需要完全缓冲其内容;你不知道listOfAccounts有多大,如果它比缓冲区大,你就会死锁。WaitGroups通常用于goroutines,而不是通道中的数据。

相反,通过关闭频道来表示您已完成添加到频道。关闭频道不会删除频道中已有的内容。它只是让读者知道不要等待来自该频道的更多数据。

现在,你可以在goroutine中馈送jobsChannel,同时也可以处理它。这避免了必须缓冲所有内容,并且你的工人和馈送器可以按照你喜欢的任何顺序设置。

workers := 10
jobs := 1000
// You don't need a buffer, but a small one doesn't hurt.
jobsChannel := make(chan int, workers*2)
go func() {
defer close(jobsChannel);
for account := 0; account < jobs; account++ {
jobsChannel <- account
}
}();

然后使用WaitGroups等待,直到所有goroutines完成。当通道关闭并且通道缓冲区中没有更多数据时,每个goroutine都将结束。

var wg sync.WaitGroup;
for i := 0; i < workers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for account := range jobsChannel {
fmt.Println(account)
}
}()
}
wg.Wait()

最新更新