通道中缺少数据

时间:2018-12-03 00:54:33

标签: go concurrency channel goroutine

我写了一个小程序练习go频道。

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}。为什么会丢失?

2 个答案:

答案 0 :(得分:4)

因为在serve()中,循环变量用于在单独的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完成,因此您可能看不到所有已打印的请求。看到这个问题,您如何才能等待启动的goroutine:Prevent the main() function from terminating before goroutines finish in Golang

答案 1 :(得分:1)

这是因为当匿名执行时,请求要求已从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
    }
}