Go语言中的通道死锁问题

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

Channel Deadlock in Go

问题

我在下面的代码中遇到了一个“fatal error: all goroutines are asleep - deadlock!”的错误。我正在使用带缓冲的通道,应该是非阻塞的。不确定我做错了什么。

package main

import (
	"fmt"
	"sync"
)

func main() {
	c := make(chan int, 2)
	var wg sync.WaitGroup
	wg.Add(2)

	go doSomething(c, wg)
	go doSomething(c, wg)
	go doSomething(c, wg)

	wg.Wait()

	close(c)

	for v := range c {
		fmt.Print(v)
	}

}

func doSomething(c chan<- int, wg sync.WaitGroup) {
	defer wg.Done()
	c <- 1

}

Playground链接:https://play.golang.org/p/J9meD5aKna

英文:

I am getting "fatal error: all goroutines are asleep - deadlock!
" for some reason in the below code. I am using buffered channel which should be non-blocking. Not sure what I am doing wrong

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
)

func main() {
	c := make(chan int, 2)
	var wg sync.WaitGroup
	wg.Add(2)

	go doSomething(c, wg)
	go doSomething(c, wg)
	go doSomething(c, wg)

	wg.Wait()

	close(c)

	for v := range c {
		fmt.Print(v)
	}

}

func doSomething(c chan&lt;- int, wg sync.WaitGroup) {
	defer wg.Done()
	c &lt;- 1

}

Playground link https://play.golang.org/p/J9meD5aKna

答案1

得分: 1

虽然你的解决方案可能有效,但我对它并不满意。

首先,需要更改通道大小才能使其正常工作,这表明可能存在潜在的问题/错误。现在,每次你想要启动另一个doSomething时,都必须记得更改通道的长度。

其次,在从通道读取之前,你要等待所有的goroutine都完成。这有点"浪费",因为使用range循环遍历通道的主要优点之一是你不必等待所有的项都生成(写入通道),你可以在其中一些项准备好时就开始处理它们。

所以我会将你的代码写成这样:

func main() {
    c := make(chan int)

    var wg sync.WaitGroup
    wg.Add(3)
    go func() {
        doSomething(c)
        defer wg.Done()
    }()
    go func() {
        doSomething(c)
        defer wg.Done()
    }()
    go func() {
        doSomething(c)
        defer wg.Done()
    }()

    go func() {
        wg.Wait()
        defer close(c)
    }()

    for v := range c {
        fmt.Print(v)
    }
}

func doSomething(c chan<- int) {
    c <- 1
}

请注意,等待和关闭通道现在在它自己的goroutine中进行 - 这允许立即开始迭代通道(现在是无缓冲的!)。

我还更改了代码,使得WaitGroup永远不会离开它声明的作用域(即它不作为参数使用),这是我的个人偏好。我认为这样可以使代码更易于理解和跟踪。

英文:

While your solution might work I'm not happy with it.

First, the fact that you need to change the channel size to make it work indicates that there is a potential problem / bug. Now each time you want to launch another doSomething you have to remember to change channel's length.

Second, you wait until all the goroutines are finished before reading from the channel. This is kind of "waste" as one of the main points of range loop over an channel is that you don't have to wait until all the items are generated (written into channel), you can start processing the items as soon as some of them are ready.

So I would write your code as something like

func main() {
	c := make(chan int)

	var wg sync.WaitGroup
	wg.Add(3)
	go func() {
		doSomething(c)
		defer wg.Done()
	}()
	go func() {
		doSomething(c)
		defer wg.Done()
	}()
	go func() {
		doSomething(c)
		defer wg.Done()
	}()

	go func() {
		wg.Wait()
		defer close(c)
	}()

	for v := range c {
		fmt.Print(v)
	}
}

func doSomething(c chan&lt;- int) {
	c &lt;- 1
}

https://play.golang.org/p/T3dfiztKot

Note how the waiting and closing the channel is now in it's own goroutine - this allows to start iterating over the channel (which is now unbuffered!) right away.

I also changed the code so that the WaitGroup never leaves the scope where it is declared (ie it isn't used as parameter), this is my personal preference. I believe it makes code easier to follow and understand.

答案2

得分: 0

我找到了问题。实际上有两个问题:

  1. 通道(channel)和 wg 的大小应该是 3。

  2. 我应该将 wg 作为指针传递。

更新后的代码:

package main

import (
	"fmt"
	"sync"
)

func main() {
	c := make(chan int, 3)
	var wg sync.WaitGroup
	wg.Add(3)

	go doSomething(c, &wg)
	go doSomething(c, &wg)
	go doSomething(c, &wg)

	wg.Wait()

	close(c)

	for v := range c {
		fmt.Print(v)
	}

}

func doSomething(c chan<- int, wg *sync.WaitGroup) {
	defer wg.Done()
	c <- 1

}
英文:

I found the problem. Actually two problems

  1. The size of the channel and wg should be 3

  2. I should pass the wg as pointer

Updated code

package main

import (
	&quot;fmt&quot;
	&quot;sync&quot;
)

func main() {
	c := make(chan int, 3)
	var wg sync.WaitGroup
	wg.Add(3)

	go doSomething(c, &amp;wg)
	go doSomething(c, &amp;wg)
	go doSomething(c, &amp;wg)

	wg.Wait()

	close(c)

	for v := range c {
		fmt.Print(v)
	}

}

func doSomething(c chan&lt;- int, wg *sync.WaitGroup) {
	defer wg.Done()
	c &lt;- 1

}

答案3

得分: 0

是的,你的代码中有一个重要的问题。

你在调用go doSomething(c, wg)时只传递了wg的值。
你应该知道

> 在Go中,参数总是按值传递。当参数可能被修改时,请使用指针。

所以你应该这样做go doSomething(c, &amp;wg),然后func doSomething(c chan&lt;- int, wg sync.WaitGroup)中的defer wg.Done()会修改main函数中的wg

.

英文:

Yes, there is an important issues in your code.

you call go doSomething(c, wg) just pass wg value.
you should know

> Arguments in Go are always passed by value. Use a pointer when an
> argument may be modified.

so you should do like this go doSomething(c, &amp;wg), then wg in main function will be modified by defer wg.Done() in func doSomething(c chan&lt;- int, wg sync.WaitGroup).

.

huangapple
  • 本文由 发表于 2017年2月7日 16:38:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/42085173.html
匿名

发表评论

匿名网友

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

确定