通道中缺少数据
Missing data in the channel
我写了一个小程序来练习go channel。
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}。为什么这个不见了?
因为在 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 上试试这个。
这在几个相关问题中有详细说明:
同样正如 Zan Lynx 指出的那样,您的主协程不会等待所有已启动的协程完成,因此您可能看不到打印的所有请求。请参阅此问题如何等待启动的 goroutines:
这是因为匿名执行时,请求已经从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
}
}
我写了一个小程序来练习go channel。
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}。为什么这个不见了?
因为在 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 上试试这个。
这在几个相关问题中有详细说明:
同样正如 Zan Lynx 指出的那样,您的主协程不会等待所有已启动的协程完成,因此您可能看不到打印的所有请求。请参阅此问题如何等待启动的 goroutines:
这是因为匿名执行时,请求已经从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
}
}