我不明白为什么这个代码可以使用无缓冲通道,并且为什么需要等待组。

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

I don't understand why this works with an unbuffered channel, or why a wait group is needed

问题

在这段代码中,我调用了一个函数来计算字符串中字母的数量,并返回一个符文的映射。为了利用并发性,我使用goroutines来调用该函数:

func ConcurrentFrequency(l []string) FreqMap {
	var wg sync.WaitGroup
	wg.Add(len(l))
	m := FreqMap{}

	// 使用无缓冲通道
	// ch := make(chan FreqMap, len(l))
	ch := make(chan FreqMap)

	for _, s := range l {
		go func(s string, ch chan<- FreqMap) {
			defer wg.Done()
			ch <- Frequency(s)
		}(s, ch)
	}
	go func() {
		wg.Wait()
		close(ch)
	}()

	for cm := range ch {
		for r, n := range cm {
			m[r] += n
		}
	}

	return m
}

如果我尝试在不使用waitgroup和关闭通道的goroutine的情况下运行此代码:

    go func() {
        wg.Wait()
        close(ch)
    }()

那么我会遇到死锁问题。

我不明白的是,为什么我能够循环遍历无缓冲通道,并从中读取多个映射。

这是完整的程序:
https://go.dev/play/p/zUwr_HvTT5w

并发方法几乎比顺序方法快不了多少:

goos: linux
goarch: amd64
pkg: letter
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkSequentialFrequency
BenchmarkSequentialFrequency-2              2820            367128 ns/op           17571 B/op         13 allocs/op
BenchmarkConcurrentFrequency
BenchmarkConcurrentFrequency-2              4237            282632 ns/op           12682 B/op         72 allocs/op
PASS
ok      letter  3.320s
英文:

In this code, I call a function which counts the number of letters in a string, and return a map of runes. To leverage concurrency, I call the function using goroutines:

func ConcurrentFrequency(l []string) FreqMap {
	var wg sync.WaitGroup
	wg.Add(len(l))
	m := FreqMap{}

	// Using unbuffered channel
	// ch := make(chan FreqMap, len(l))
	ch := make(chan FreqMap)

	for _, s := range l {
		go func(s string, ch chan&lt;- FreqMap) {
			defer wg.Done()
			ch &lt;- Frequency(s)
		}(s, ch)
	}
	go func() {
		wg.Wait()
		close(ch)
	}()

	for cm := range ch {
		for r, n := range cm {
			m[r] += n
		}
	}

	return m
}

If I try this code without using a waitgroup and the goroutine which closes the channel:

    go func() {
        wg.Wait()
        close(ch)
    }()

, then I get a deadlock.

What I don't understand, is why I am able to loop over the unbuffered channel, and read multiple maps from it.

This is the full program:
https://go.dev/play/p/zUwr_HvTT5w

And the concurrent method is barely faster than the sequential method:

goos: linux
goarch: amd64
pkg: letter
cpu: Intel(R) Core(TM) i5-6200U CPU @ 2.30GHz
BenchmarkSequentialFrequency
BenchmarkSequentialFrequency-2              2820            367128 ns/op           17571 B/op         13 allocs/op
BenchmarkConcurrentFrequency
BenchmarkConcurrentFrequency-2              4237            282632 ns/op           12682 B/op         72 allocs/op
PASS
ok      letter  3.320s

答案1

得分: 2

A for-range循环在通道关闭之前一直持续

如果你移除最终关闭通道的goroutine,for循环将永远无法终止。一旦所有发送值的goroutine完成,只剩下一个goroutine,并且它会永远被阻塞,等待通道关闭。

缓冲通道与此问题无关。它们只有在发送者被阻塞时才有帮助,但这里的问题是接收者被阻塞。

英文:

A for-range loop over a channel continues until the channel is closed.

If you remove the goroutine that eventually closes the channel, the for loop can never terminate. Once all goroutines sending values are done there is only one goroutine remaining and it is blocked forever, waiting for the channel to be closed.

Buffered channels have nothing to do with this problem. They only help with blocked senders, but here the problem is a blocked receiver.

huangapple
  • 本文由 发表于 2022年5月12日 23:57:48
  • 转载请务必保留本文链接:https://go.coder-hub.com/72218744.html
匿名

发表评论

匿名网友

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

确定