我一直试图解决我在Golang并发中遇到的这个简单问题。我一直在寻找所有可能的解决方案,但没有发现我的问题(或者我可能会错过一个)。这是我的代码:
package main
import (
"fmt"
"time"
)
func producer(ch chan int, d time.Duration, num int) {
for i:=0; i<num; i++ {
ch <- i
time.Sleep(d)
}
}
func main() {
ch := make(chan int)
go producer(ch, 100*time.Millisecond, 2)
go producer(ch, 200*time.Millisecond, 5)
for {
fmt.Println(<-ch)
}
close(ch)
}
打印错误:
致命错误:所有goroutine都睡着了 - 死锁!
goroutine 1 [陈接收]: main.main() D:/Code/go/src/testconcurrency/main.go:23 + 0xca 退出状态2
避免此错误的有效方法是什么?,谢谢。
答案 0 :(得分:6)
你有短暂的生产者&#34;他们只在通道上发送有限时间的值,你有一个无限的for
循环从频道接收值无休止地,没有终止条件,并且通道仅在此无限循环之后关闭。一旦生产者停止发送价值,就会陷入僵局。
制作人必须关闭频道,表示不再发送任何值。由于你有多个生产者没有同步(生产者彼此不同步),一般你不能告诉哪一个会先完成,所以你不能指定一个关闭渠道(并且一个渠道可以只关闭一次,请参阅Why Go's channel can close twice?;以及Closing channel of unknown length)。
你必须&#34;协调&#34;生产者,当所有人完成工作时,协调员应关闭渠道。
消费者应该在频道上使用for range
,因为for range
构造会在关闭之前接收通道上发送的所有值,然后自动终止。
为了协调,建议使用sync.WaitGroup
。无论您是在这种情况下使用全局的还是本地的,都将它传递给生产者,由您自己决定。使用本地将使解决方案更通用,更容易扩展。需要注意的一点是,您必须将指针传递给sync.WaitGroup
。每当你启动一个新的生产者时,使用WaitGroup.Add()
增加等待组。当生成器完成时,它可以使用WaitGroup.Done()
发出信号,最好使用defer
(因此无论如何都会运行,在异常情况下减轻死锁)。控制器可以等待所有生成器使用WaitGroup.Wait()
完成。
这是一个完整的解决方案:
func producer(ch chan int, d time.Duration, num int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < num; i++ {
ch <- i
time.Sleep(d)
}
}
func main() {
wg := &sync.WaitGroup{}
ch := make(chan int)
wg.Add(1)
go producer(ch, 100*time.Millisecond, 2, wg)
wg.Add(1)
go producer(ch, 200*time.Millisecond, 5, wg)
go func() {
wg.Wait()
close(ch)
}()
for v := range ch {
fmt.Println(v)
}
}
输出(在Go Playground上尝试):
0
0
1
1
2
3
4
请参阅相关问题:Prevent the main() function from terminating before goroutines finish in Golang
答案 1 :(得分:5)
可以使用两个等待组以优雅的方式解决此问题。通过关闭渠道ch
,我们向消费者发出没有更多数据的信号。
解决方案适用于更多消费者。
package main
import (
"fmt"
"sync"
"time"
)
func producer(ch chan<- int, d time.Duration, num int, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < num; i++ {
ch <- i
time.Sleep(d)
}
}
func consumer(ch <-chan int, wg *sync.WaitGroup) {
defer wg.Done()
for x := range ch {
fmt.Println(x)
}
}
func main() {
ch := make(chan int)
producers := &sync.WaitGroup{}
consumers := &sync.WaitGroup{}
producers.Add(2)
go producer(ch, 100*time.Millisecond, 2, producers)
go producer(ch, 200*time.Millisecond, 5, producers)
consumers.Add(1)
go consumer(ch, consumers)
producers.Wait()
close(ch)
consumers.Wait()
}
答案 2 :(得分:3)
问题是<-ch
正在阻止,因此如果您没有向频道添加任何新值,它将永久阻止。一种方法是用开关选择替换它,该选择也是阻塞但允许在多个通道上收听。您还必须添加退出渠道。在您的示例中,只要退出通道收到两个值,我们就可以中断。 break语句需要一个标签,因为我们想从switch和for循环中退出。
https://play.golang.org/p/wGdCulZDnrx
另一种方法是拥有多个输入通道,并在完成发送后立即关闭它们。为此,每个goroutine都需要它自己的频道,否则我们将在第一个goroutine完成时退出。
第三个选项是创建一个合并函数,将多个通道合并为一个。这允许将通道的创建移动到生产者中,因此它们在一个位置被创建,填充和关闭。合并功能相对复杂,但它从业务逻辑代码中删除,可以单独理解和测试。然后主要代码简化为:
ch1 := producer(100*time.Millisecond, 2)
ch2 := producer(200*time.Millisecond, 5)
for i := range merge(ch1, ch2) {
fmt.Println(i)
}
https://play.golang.org/p/2mv8ILhJPIB
merge func来自https://blog.golang.org/pipelines
答案 3 :(得分:1)
您需要同步goroutines中的所有异步进程。您的主线程和goroutine线程不是同步进程。你的主线程永远不会知道何时停止从goroutines调用通道。由于主线程在通道上循环,它总是调用通道中的值,当goroutines完成并且通道停止发送值时,主线程无法再从通道获取值,因此条件变为死锁。为避免这种情况,请使用sync.WaitGroup
来同步异步进程。
以下是代码:
package main
import (
"fmt"
"time"
"sync"
)
func producer(ch chan int, d time.Duration, num int, wg *sync.WaitGroup) {
for i:=0; i<num; i++ {
ch <- i;
time.Sleep(d);
}
defer wg.Done();
}
func main() {
wg := &sync.WaitGroup{}
ch := make(chan int);
wg.Add(2);
go producer(ch, 100*time.Millisecond, 2, wg);
go producer(ch, 200*time.Millisecond, 5, wg);
go func() {
wg.Wait()
close(ch)
}()
// print the outputs
for i:= range ch {
fmt.Println(i);
}
}
https://play.golang.org/p/euMTGTIs83g
希望它有所帮助。
由于我的解决方案与已经回答的解决方案看起来有点类似,因此在修改之前将其更改为我原来的答案,以适应OP问题。
以下是代码:
package main
import (
"fmt"
"time"
"sync"
)
// producer produce values tobe sent to consumer
func producer(ch chan int, d time.Duration, num int, wg *sync.WaitGroup) {
defer wg.Done();
for i:=0; i<num; i++ {
ch <- i;
time.Sleep(d);
}
}
// consumer consume all values from producers
func consumer(ch chan int, out chan int, wg *sync.WaitGroup) {
defer wg.Done();
for i:= range ch {
out <- i
}
}
// synchronizer synchronize all goroutines to avoid deadlocks
func synchronizer(ch chan int, out chan int, wgp *sync.WaitGroup, wgc *sync.WaitGroup) {
wgp.Wait()
close(ch)
wgc.Wait()
close(out)
}
func main() {
wgp := &sync.WaitGroup{}
wgc := &sync.WaitGroup{}
ch := make(chan int);
out := make(chan int);
wgp.Add(2);
go producer(ch, 100*time.Millisecond, 2, wgp);
go producer(ch, 200*time.Millisecond, 5, wgp);
wgc.Add(1);
go consumer(ch, out, wgc)
go synchronizer(ch, out, wgp, wgc)
// print the outputs
for i:= range out {
fmt.Println(i);
}
}
使用consumer
goroutine fan-in
来自多个goroutine的所有输入,并从consumer
goroutine中读取所有值。
希望它有所帮助。
答案 4 :(得分:0)
更简单的答案 - 其中一个制片人需要关闭频道,而消费者可以只关注频道。
package main
import (
"fmt"
"time"
)
func producer(ch chan int, d time.Duration, num int, closer bool) {
for i:=0; i<num; i++ {
ch <- i
time.Sleep(d)
}
if closer {
close(ch)
}
}
func main() {
ch := make(chan int)
go producer(ch, 100*time.Millisecond, 2, false)
go producer(ch, 200*time.Millisecond, 5, true)
for i := range ch {
fmt.Println(i)
}
}
当然,除非您知道哪个制作人总是最后完成,否则您不希望在实际代码中执行此操作。在其他答案中,更好的设计是基于WaitGroup的模式。但这是此代码避免死锁的最简单方法。