英文:
Channel race condition
问题
这似乎是一个关于通道的非常基本的问题。我已经阅读到无缓冲通道在发送时会阻塞,那么为什么这段代码会有竞争条件呢?
有时输出是这样的:
<- CREATE;
<- INSERT;
END
[CREATE; INSERT;]
有时输出中缺少了"INSERT;",但它确实被写入了通道。
<- CREATE;
<- INSERT;
END
[CREATE; INSERT;]
如果Split
函数在从SplitChan
的cmd := <-c
处阻塞,那么它如何返回呢?
package main
import "fmt"
func main() {
batch := `CREATE;INSERT;`
res := Split(batch)
fmt.Print(res)
}
func SplitChan(batch string, outc chan<- string) {
b := 0
for i, c := range batch {
switch c {
case ';':
cmd := batch[b : i+1]
fmt.Println("<- " + cmd)
b = i + 1
outc <- cmd
}
}
fmt.Println("END")
}
func Split(batch string) []string {
var res []string
c := make(chan string)
go func() {
for {
cmd := <-c
res = append(res, cmd)
}
}()
SplitChan(batch, c)
close(c)
return res
}
Playground链接:https://go.dev/play/p/WmO5OtmgETl
我期望每次运行都得到相同的输出:
<- CREATE;
<- INSERT;
END
[CREATE; INSERT;]
我在这里漏掉了什么?
谢谢。
英文:
This locks like a rather very basic problem regarding to channels. I have read that unbuffered channels block on send, so why this code has a race-condition?
Sometimes the output is
<- CREATE;
<- INSERT;
END
[CREATE; INSERT;]
Sometime the "INSERT;" is missing from the output, but it's written to the channel.
<- CREATE;
<- INSERT;
END
[CREATE; INSERT;]
How can the SplitChan
func return, if it blocks on cmd := <-c
from Slit
?
package main
import "fmt"
func main() {
batch := `CREATE;INSERT;`
res := Split(batch)
fmt.Print(res)
}
func SplitChan(batch string, outc chan<- string) {
b := 0
for i, c := range batch {
switch c {
case ';':
cmd := batch[b : i+1]
fmt.Println("<- " + cmd)
b = i + 1
outc <- cmd
}
}
fmt.Println("END")
}
func Split(batch string) []string {
var res []string
c := make(chan string)
go func() {
for {
cmd := <-c
res = append(res, cmd)
}
}()
SplitChan(batch, c)
close(c)
return res
}
Playground Link: https://go.dev/play/p/WmO5OtmgETl
I am expected the same output on every run:
<- CREATE;
<- INSERT;
END
[CREATE; INSERT;]
What am I missing here?
Thank you
答案1
得分: 3
当SplitChan
向通道写入数据时,正在进行的goroutine中的追加操作和语句return res
是并发的。return res
可能会看到包含一个元素或两个元素的切片。
你必须确保在返回之前,追加操作已经完成:
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
for cmd := range c {
res = append(res, cmd)
}
}()
SplitChan(batch, c)
close(c)
wg.Wait()
return res
WaitGroup确保在goroutine完成后函数才返回。
英文:
When SplitChan
writes to the channel, the append that's happening at the goroutine and the statement return res
are concurrent. return res
may see the slice with one element or two elements.
You have to make sure that before returning, the append operation is completed:
wg:=sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
for cmd := range c {
res = append(res, cmd)
}
}()
SplitChan(batch, c)
close(c)
wg.Wait()
return res
The WaitGroup ensures that the function returns after the goroutine is done.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论