【问题标题】:Missing data in the channel通道中缺少数据
【发布时间】:2018-12-03 00:54:32
【问题描述】:

我写了一个小程序来练习围棋频道。

package main

import (
    "log"
    "strconv"
)

var MaxOutstanding int = 1
var channelSize int = 10
var sem = make(chan int, MaxOutstanding)

type Request struct {
    command string
    data    string
}

func process(req *Request) {
    log.Println(req)
}

func serve(reqs chan *Request) {
    for req := range reqs {
        sem <- 1
        go func() {
            process(req)
            <-sem
        }()
    }
}

func main() {
    reqs := make(chan *Request, channelSize)
    for i := 0; i < channelSize; i++ {
        req := &Request{"start", strconv.Itoa(i)}
        reqs <- req
    }
    close(reqs)
    serve(reqs)
}

打印出来

2018/12/02 16:52:30 &{start 1}
2018/12/02 16:52:30 &{start 2}
2018/12/02 16:52:30 &{start 3}
2018/12/02 16:52:30 &{start 4}
2018/12/02 16:52:30 &{start 5}
2018/12/02 16:52:30 &{start 6}
2018/12/02 16:52:30 &{start 7}
2018/12/02 16:52:30 &{start 8}
2018/12/02 16:52:30 &{start 9}

因此,&{start 0} 不会被打印。这个怎么不见了?

【问题讨论】:

  • 你对sem 的看法很奇怪。如果您希望频道上的读者数量有限,那么只启动那么多读者。每个读者都会阅读reqs。嗯,随便。您的 sem 与我读过的其他 Go 代码不同。
  • 反正我是这样写的,不是你必须这样写:play.golang.org/p/RB3sUoSaSxm
  • @ZanLynx 感谢您的额外建议!

标签: go concurrency channel goroutine


【解决方案1】:

因为在serve() 中,您的循环变量在您在单独的 goroutine 上执行的函数文字中使用,该 goroutine 同时由运行循环的 goroutine 修改:数据竞争。如果您有数据竞争,则行为未定义。

如果您复制变量,它将起作用:

for req := range reqs {
    sem <- 1
    req2 := req
    go func() {
        process(req2)
        <-sem
    }()
}

Go Playground 上试用。

另一种可能性是将其作为参数传递给匿名函数:

for req := range reqs {
    sem <- 1
    go func(req *Request) {
        process(req)
        <-sem
    }(req)
}

Go Playground 上试试这个。

这在几个相关问题中有详细说明:

Using Pointers in a for Loop - Golang

Golang: Register multiple routes using range for loop slices/map

Why do these two for loop variations give me different behavior?

正如 Zan Lynx 所指出的,您的主 goroutine 不会等待所有启动的 goroutine 完成,因此您可能看不到所有打印的请求。请参阅这个问题,如何等待已启动的 goroutines:Prevent the main() function from terminating before goroutines finish in Golang

【讨论】:

  • 我注意到你的(和原始代码)没有做 9,因为它没有等待足够长的时间让 goroutines 完成。
  • @ZanLynx 是的,你说得对,我只关注有问题的问题。
  • 我意识到这些错误也在“有效围棋”文章中讨论过。但是,在阅读完文章后,我仍然不明白第二个建议是如何工作的。即, req 是一个指针,而 for 循环仍在更改它,这与我的代码中的情况相同。唯一的区别是 req 在闭包中作为参数传递。你能详细说明它是如何工作的吗?
  • @drdot 添加了 3 个相关问题的详细链接。
【解决方案2】:

这是因为匿名执行时,req 已经从Request{0} 移动到Request{1},所以你从{start 1} 打印。

// method 1: add a parameter, pass it to anonymous function
func serve(reqs chan *Request) {
    for req := range reqs {
        sem <- 1

        // You should make the req as parameter of this function
        // or, when the function execute, the req point to Request{1}
        go func(dup *Request) {
            process(dup)
            <-sem
        }(req)
    }

    // And you should wait for the Request{9} to be processed
    time.Sleep(time.Second)
}

// method 2: make for wait anonymous function
func serve(reqs chan *Request) {
    for req := range reqs {
        go func() {
            process(req)
            sem <- 1
        }()

        // Or wait here
        <-sem
    }
}

【讨论】:

    猜你喜欢
    • 2020-10-24
    • 2019-10-19
    • 1970-01-01
    • 1970-01-01
    • 2018-05-07
    • 2014-12-04
    • 2012-11-25
    • 2021-09-04
    • 2014-03-14
    相关资源
    最近更新 更多