拉0大小的golang陈

时间:2018-01-24 09:57:34

标签: 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 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
            }
    }
}

2 个答案:

答案 0 :(得分:1)

这是因为你正在关闭缓冲区,但你的notifySubscriber仍在监听缓冲区。关闭的通道始终返回默认类型值(在这种情况下为空Subscription,空TargetURL)。因此,你得到一个空字符串。

方案:

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

答案 1 :(得分:1)

来自规范:

  

对于通道c,内置函数close(c)不再记录   值将在频道上发送。如果c是a则是错误的   仅接收频道。发送或关闭封闭的通道会导致a   运行时恐慌。关闭零通道也会导致运行时恐慌。   在调用close之后,以及之前发送的任何值之后   收到,接收操作将返回零值   通道的类型没有阻塞。多值接收操作   返回一个接收的值以及是否指示   频道已关闭。

最后一句话表示sub, more := <-buffer,如果false已关闭,则会buffer更多。

但是,在您的情况下,代码可以使用一些改进。

首先,使用只有一个select的{​​{1}}语句是没有意义的。如果没有case,它的行为就会一样。

其次,在保证接收频道返回的情况下,可以使用select过频道。所以你的代码可以改为:

range