go - 拉 0 大小的 golang chan

标签 go race-condition channel goroutine

我的用例如下:我需要向 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 通知失败。错误:一些严重的错误 2018/01/24 10:52:48 缓冲区大小:1 2018/01/24 10:52:48 发送通知给 2018/01/24 10:52:48 通知失败。错误:一些严重的错误 2018/01/24 10:52:48 缓冲区大小:0 2018/01/24 10:52:48 发送通知给 2018/01/24 10:52:48 通知失败。错误:一些严重的错误 ...等等,直到我 SIGINT 程序

所以基本上这意味着我已经成功地将通知发送给了正确的人,但我仍然继续发送到空的 targetUrl,因为我从一个空的 chan 中读取。

怎么了?

[编辑] 解决方法,但我不喜欢它

for {
    select {
        case sub, more := <-buffer:
            if !more {
                return
            }
    }
}

最佳答案

这是因为您正在关闭缓冲区,但您的 notifySubscriber 仍在监听缓冲区。关闭的 channel 始终返回默认类型值(在本例中为空 Subscription 和空 TargetURL)。因此,您得到一个空字符串。

场景:

  • 如果你想保持 goroutines 运行,那么不要关闭缓冲区。
  • 工作完成后停止 goroutine,然后关闭缓冲区。

关于go - 拉 0 大小的 golang chan,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48419499/

相关文章:

c++ - 如何获取当前DLL的目录

google-app-engine - blobstore.ParseUpload 在开发服务器和部署中的行为不同

gorm如何外国 self

c++ - std::thread 的 drd 和 helgrind 支持的当前状态

select - Go select 语句解决方法中的优先级

for-loop - 在 Go 中使用由 'for' 循环创建的 channel

go - throw 所有的 goroutines 都睡着了——僵局! ------- 谷歌GO的错误

c# - C# quartz 竞赛条件

c++ - 读写竞争条件会改变同时读取和写入的数据吗?

go - 编写深度为 d 的嵌套迭代器