【问题标题】:Pulling 0-sized golang chan拉0大小的golang chan
【发布时间】:2018-07-03 07:44:13
【问题描述】:

我的用例如下:我需要向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,因为我从一个空的 chan 读取。

怎么了?

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

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

【问题讨论】:

    标签: go race-condition channel goroutine


    【解决方案1】:

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

    场景:

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

    【讨论】:

    • 那么我的编辑是否有效?这样 goroutine 就会自行关闭
    • 有效,是的
    【解决方案2】:

    来自规范:

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

    最后一句的意思是sub, more := &lt;-buffer,如果buffer被关闭,更多的将是false

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

    首先,在只有一个case 的情况下使用select 语句是没有意义的。如果没有select,它的行为也会一样。

    其次,在保证接收通道返回的情况下,可以使用range over channel。所以你的代码可以改成:

    func notifySubscriber(buffer chan Subscription) {
        log.Println("notifySubscriber")
        for sub:= range buffer {
            //Code here...
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2015-08-31
      • 2014-10-24
      • 2018-12-16
      • 2013-09-13
      • 2019-03-14
      • 2021-11-16
      • 1970-01-01
      • 2018-10-13
      • 1970-01-01
      相关资源
      最近更新 更多