我的用例如下:我需要向0...N
订阅者发送POST请求,这些订阅者由targetUrl表示。我想限制goroutine的最大数量,比如100,我的代码(简化)如下:
package main
import (
"fmt"
"log"
"net/http"
"errors"
)
const MAX_CONCURRENT_NOTIFICATIONS = 100
type Subscription struct {
TargetUrl string
}
func notifySubscribers(subs []Subscription) {
log.Println("notifySubscribers")
var buffer = make(chan Subscription, len(subs))
defer close(buffer)
for i := 0; i < MAX_CONCURRENT_NOTIFICATIONS; i++ {
go notifySubscriber(buffer)
}
for i := range subs {
buffer <- subs[i]
}
}
func notifySubscriber(buffer chan Subscription) {
log.Println("notifySubscriber")
for {
select {
case sub := <-buffer:
log.Println("sending notification to " + sub.TargetUrl)
resp, err := failPost()
if err != nil {
log.Println(fmt.Sprintf("failed to notify %s. error: %s", sub.TargetUrl, err.Error()))
} else {
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Println(fmt.Sprintf("%s responded with %d", sub.TargetUrl, resp.StatusCode))
}
}
}
log.Println(fmt.Sprintf("buffer size: %d", len(buffer)))
}
}
func failPost() (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusBadRequest,
}, errors.New("some bad error")
}
func main() {
log.Println("main")
var subs []Subscription
subs = append(subs, Subscription{TargetUrl: "http://foo.bar"})
subs = append(subs, Subscription{TargetUrl: "http://fizz.buzz"})
notifySubscribers(subs)
select {}
}
输出如下:
2018/01/24 10:52:48 failed to notify . error: some bad error
2018/01/24 10:52:48 buffer size: 1
2018/01/24 10:52:48 sending notification to
2018/01/24 10:52:48 failed to notify . error: some bad error
2018/01/24 10:52:48 buffer size: 0
2018/01/24 10:52:48 sending notification to
2018/01/24 10:52:48 failed to notify . error: some bad error
... and so on till I SIGINT the program
所以基本上它意味着我已成功地将通知发送给合适的人,但我仍然继续发送到空的targetUrl,因为我从一个空的陈中读取。
有什么问题?
[编辑]解决方法,但我不喜欢
for {
select {
case sub, more := <-buffer:
if !more {
return
}
}
}
答案 0 :(得分:1)
这是因为你正在关闭缓冲区,但你的notifySubscriber
仍在监听缓冲区。关闭的通道始终返回默认类型值(在这种情况下为空Subscription
,空TargetURL
)。因此,你得到一个空字符串。
方案:
答案 1 :(得分:1)
来自规范:
对于通道c,内置函数close(c)不再记录 值将在频道上发送。如果c是a则是错误的 仅接收频道。发送或关闭封闭的通道会导致a 运行时恐慌。关闭零通道也会导致运行时恐慌。 在调用close之后,以及之前发送的任何值之后 收到,接收操作将返回零值 通道的类型没有阻塞。多值接收操作 返回一个接收的值以及是否指示 频道已关闭。
最后一句话表示sub, more := <-buffer
,如果false
已关闭,则会buffer
更多。
但是,在您的情况下,代码可以使用一些改进。
首先,使用只有一个select
的{{1}}语句是没有意义的。如果没有case
,它的行为就会一样。
其次,在保证接收频道返回的情况下,可以使用select
过频道。所以你的代码可以改为:
range