通道竞争条件

huangapple go评论108阅读模式
英文:

Channel race condition

问题

这似乎是一个关于通道的非常基本的问题。我已经阅读到无缓冲通道在发送时会阻塞,那么为什么这段代码会有竞争条件呢?

有时输出是这样的:

<- CREATE;
<- INSERT;
END
[CREATE; INSERT;]

有时输出中缺少了"INSERT;",但它确实被写入了通道。

<- CREATE;
<- INSERT;
END
[CREATE; INSERT;]

如果Split函数在从SplitChancmd := <-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

&lt;- CREATE;
&lt;- INSERT;
END
[CREATE; INSERT;]

Sometime the "INSERT;" is missing from the output, but it's written to the channel.

&lt;- CREATE;
&lt;- INSERT;
END
[CREATE; INSERT;]

How can the SplitChan func return, if it blocks on cmd := &lt;-c from Slit?

package main

import &quot;fmt&quot;

func main() {
	batch := `CREATE;INSERT;`
	res := Split(batch)
	fmt.Print(res)
}

func SplitChan(batch string, outc chan&lt;- string) {
	b := 0
	for i, c := range batch {
		switch c {
		case &#39;;&#39;:
			cmd := batch[b : i+1]
			fmt.Println(&quot;&lt;- &quot; + cmd)
			b = i + 1
			outc &lt;- cmd
		}
	}

	fmt.Println(&quot;END&quot;)
}

func Split(batch string) []string {
	var res []string
	c := make(chan string)
	go func() {
		for {
			cmd := &lt;-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:

&lt;- CREATE;
&lt;- 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.

huangapple
  • 本文由 发表于 2022年12月22日 03:13:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/74880795.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定