为何需额外遍历Go协程/通道结果才能显示全部数据?
db.Feeds()才能打印所有RSS结果? 嘿,作为Go语言新手遇到这个问题太正常啦,我来给你把这个逻辑掰明白~
首先,咱们先拆解下你的代码逻辑和Go语言通道的核心特性:
1. 通道的发送与接收是「一对一」的
Go里的无缓冲通道(你这里make(chan feed)创建的就是无缓冲通道),每一次发送操作都必须对应一次接收操作才能完成。简单说就是:你往通道里塞1个数据,就得有1个接收动作把它取走,不然发送的goroutine会一直阻塞在那里。
你的第一个循环:
for _, url := range db.Feeds() { go getFeeds(url, rss) }
这里启动了N个goroutine(N是db.Feeds()返回的URL数量),每个goroutine都会调用getFeeds,最终往rss通道发送1个feed数据。也就是说,通道里最终会有N个待接收的数据。
2. 单次接收只能拿一个数据
如果你只写一次:
newFeed := <-rss fmt.Println(...)
这只会从通道里取出第一个被发送进来的数据,然后主goroutine就会继续往下走(如果后面没代码就直接退出程序了)。剩下的N-1个goroutine要么还在阻塞等待发送(因为没人接收它们的数据),要么已经发送完但数据留在通道里没人取,但主程序已经退出,这些数据永远没机会被处理。
3. 第二个循环的作用:完成N次接收
你代码里的第二个循环:
for range db.Feeds() { newFeed := <-rss fmt.Println(newFeed.Channel.Title, newFeed.Channel.Description) }
它的本质是循环N次(和URL数量一致),每次循环执行一次接收操作,把通道里的N个数据逐个取出来打印。这样所有goroutine发送的数据都能被处理,也不会有goroutine一直阻塞(主程序退出前会等所有接收完成)。
额外补充:缓冲通道的情况
如果你把通道改成有缓冲的,比如:
rss := make(chan feed, len(db.Feeds()))
那goroutine可以先把所有N个数据都塞进通道的缓冲里,不用立刻等待接收。但即便如此,你还是需要做N次接收操作才能把所有数据取出来——缓冲只是让发送和接收不用严格同步,但数据的数量还是对应接收次数的。
总结一下:你启动了N个goroutine发N个数据,就需要N次接收操作来处理它们,第二个循环就是帮你完成这N次接收的,所以必须要写它才能打印全部结果~
内容的提问来源于stack exchange,提问作者Chris

