[英]Why is my code causing a stall or race condition?
由于某种原因,一旦我开始通过goroutine中的通道添加字符串,则在运行该代码时会停顿。 我认为这是一个范围/关闭问题,所以我将所有代码直接移入该函数均无济于事。 我浏览了Golang的文档,所有示例看起来都与我的相似,因此对于出了什么问题我一无所知。
func getPage(url string, c chan<- string, swg sizedwaitgroup.SizedWaitGroup) {
defer swg.Done()
doc, err := goquery.NewDocument(url)
if err != nil{
fmt.Println(err)
}
nodes := doc.Find(".v-card .info")
for i := range nodes.Nodes {
el := nodes.Eq(i)
var name string
if el.Find("h3.n span").Size() != 0{
name = el.Find("h3.n span").Text()
}else if el.Find("h3.n").Size() != 0{
name = el.Find("h3.n").Text()
}
address := el.Find(".adr").Text()
phoneNumber := el.Find(".phone.primary").Text()
website, _ := el.Find(".track-visit-website").Attr("href")
//c <- map[string] string{"name":name,"address":address,"Phone Number": phoneNumber,"website": website,};
c <- fmt.Sprint("%s%s%s%s",name,address,phoneNumber,website)
fmt.Println([]string{name,address,phoneNumber,website,})
}
}
func getNumPages(url string) int{
doc, err := goquery.NewDocument(url)
if err != nil{
fmt.Println(err);
}
pagination := strings.Split(doc.Find(".pagination p").Contents().Eq(1).Text()," ")
numItems, _ := strconv.Atoi(pagination[len(pagination)-1])
return int(math.Ceil(float64(numItems)/30))
}
func main() {
arrChan := make(chan string)
swg := sizedwaitgroup.New(8)
zips := []string{"78705","78710","78715"}
for _, item := range zips{
swg.Add()
go getPage(fmt.Sprintf(base_url,item,1),arrChan,swg)
}
swg.Wait()
}
编辑:所以我通过传递sizewaitgroup作为参考来修复它,但是当我删除缓冲区不起作用时,这意味着我需要提前知道将多少个元素发送到通道吗?
据我所知,根据您发布的代码,以科林·斯图尔特的答案为基础,实际上,您遇到的问题是阅读arrChan
。 您可以编写代码,但是在代码中没有地方可以读取它。
从文档中 :
如果通道未缓冲,则发送方将阻塞,直到接收方收到该值为止。 如果通道具有缓冲区,则发送方仅在将值复制到缓冲区之前才阻塞; 如果缓冲区已满,则意味着要等到某些接收者检索到一个值。
通过缓冲通道,发生的事情是您的代码不再阻塞通道写操作,该行如下所示:
c <- fmt.Sprint("%s%s%s%s",name,address,phoneNumber,website)
我的猜测是,当通道大小为5000时,如果您仍然不知所措,那是因为在node.Nodes
所有循环中返回的值都超过5000。 一旦缓冲通道已满,操作就会阻塞,直到该通道有空间为止,就像您正在写入非缓冲通道一样。
这是一个最小的示例,向您展示如何解决此问题(基本上只是添加一个阅读器)
package main
import "sync"
func getPage(item string, c chan<- string) {
c <- item
}
func readChannel(c <-chan string) {
for {
<-c
}
}
func main() {
arrChan := make(chan string)
wg := sync.WaitGroup{}
zips := []string{"78705", "78710", "78715"}
for _, item := range zips {
wg.Add(1)
go func() {
defer wg.Done()
getPage(item, arrChan)
}()
}
go readChannel(arrChan) // comment this out and you'll deadlock
wg.Wait()
}
您的通道没有缓冲区,因此写操作将阻塞,直到可以读取该值为止,并且至少在您发布的代码中,没有读取器。
您无需知道尺寸即可使用。 但是您可能为了干净地退出。 有时观察起来有些棘手,因为一旦主函数退出并且所有仍在运行的goroutine被立即终止或未终止,程序就会退出。
作为热身示例,请在photoionized的响应中更改readChannel:
func readChannel(c <-chan string) {
for {
url := <-c
fmt.Println (url)
}
}
它仅将打印添加到原始代码。 但是现在您会更好地看到实际发生的情况。 请注意,通常在代码实际写入3时它通常只打印两个字符串。这是因为一旦所有写入goroutine完成,代码就会退出,但是结果会中途中断读取goroutine。 您可以通过在readChannel之前删除“ go”来“修复”它(这与在主函数中读取通道相同)。 然后,您将看到3个字符串被打印,但是由于readChannel仍从该通道读取,程序因死锁而崩溃,但是没有人再写入该通道。 您也可以通过在readChannel()中精确读取3个字符串来解决此问题,但这需要知道您希望接收多少个字符串。
这是我的最小工作示例(我将使用它来说明其余部分):
package main
import (
"fmt"
"sync"
)
func getPage(url string, c chan<- string, wg *sync.WaitGroup) {
defer wg.Done()
c <- fmt.Sprintf("Got page for %s\n",url)
}
func readChannel(c chan string, wg *sync.WaitGroup) {
defer wg.Done()
var url string
ok := true
for ok {
url, ok = <- c
if ok {
fmt.Printf("Received: %s\n", url)
} else {
fmt.Println("Exiting readChannel")
}
}
}
func main() {
arrChan := make(chan string)
var swg sync.WaitGroup
base_url := "http://test/%s/%d"
zips := []string{"78705","78710","78715"}
for _, item := range zips{
swg.Add(1)
go getPage(fmt.Sprintf(base_url,item,1),arrChan,&swg)
}
var wg2 sync.WaitGroup
wg2.Add(1)
go readChannel(arrChan, &wg2)
swg.Wait()
// All written, signal end to readChannel by closing the channel
close(arrChan)
wg2.Wait()
}
在这里,我关闭通道以向readChannel发出信号,告知您没有剩余要读取的内容,因此它可以在适当的时间干净地退出。 但是有时候您可能想要告诉readChannel准确读取3个字符串并完成。 或者,可能是您想为每个作者启动一个读者,而每个读者将只阅读一个字符串...嗯,有很多方法可以给猫咪剥皮,而选择全由您自己决定。
请注意,如果删除wg2.Wait()行,您的代码将等同于光电离的响应,并且在写入3时仅打印两个字符串。这是因为一旦所有写入器完成,代码就会退出(由swg.Wait()确保)。不要等待readChannel完成。
如果改为删除close(arrChan)行,则在打印3行代码后,代码将死锁,因为代码等待readChannel完成,但是readChannel等待从没有人写入的通道读取内容。
如果仅在readChannel调用之前删除“ go”,则等同于从主函数内部的通道读取。 打印3个字符串后,它将再次因死锁而崩溃,因为在所有写入器都已完成(且readChannel已读取所有写入内容)后,readChannel仍在读取。 棘手的一点是,此代码将永远无法到达swg.Wait()行,因为readChannel不会退出。
如果在swg.Wait()之后移动readChannel调用,则代码将崩溃,甚至无法打印单个字符串。 但这是一个不同的死锁。 这段时间代码到达swg.Wait()并在那里停止等待编写器。 第一个写入器成功,但是通道未缓冲,因此下一个写入器阻塞,直到有人从通道读取已经写入的数据为止。 问题是-由于尚未调用readChannel,因此没有人从该通道进行读取。 因此,它会死锁并死锁。 这个特殊的问题可以“解决”,但是使通道像在make(chan string, 3)
那样make(chan string, 3)
缓冲,因为即使没有人从该通道读取内容,它也可以使编写者继续写。 有时这就是您想要的。 但是在这里,您必须再次知道通道缓冲区中存在的最大消息数。 在大多数情况下,这只是在推迟问题-只添加一个编写器,而您就从这里开始-代码会停顿并崩溃,因为通道缓冲区已满,并且有一个额外的编写器正在等待有人从该缓冲区中读取数据。
好吧,这应该涵盖所有基础。 因此,请检查您的代码,看看是哪种情况。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.