解决goroutines死锁
我一直在尝试解决我在Golang并发中遇到的这个简单问题。我一直在寻找所有可能的解决方案,但没有找到任何特定于我的问题(或者我可能错过了一个)。这是我的密码:解决goroutines死锁,go,concurrency,channel,goroutine,Go,Concurrency,Channel,Goroutine,我一直在尝试解决我在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
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)
}
主程序包
进口(
“fmt”
“时间”
)
func生产者(ch chan int,d time.Duration,num int){
对于i:=0;i而言,问题在于您有“短命”的生产者,他们只在有限的时间内发送通道上的值,并且您有一个无止境的for
循环,该循环无止境地接收来自通道的值,没有终止条件,并且该通道仅在该无止境循环后关闭。一旦生产者停止发送值,这将是一个死锁
频道必须由制作者关闭,这表明不会再向其发送更多的值。由于您有多个制作者没有同步(制作者彼此不同步),通常您无法判断哪一个将首先完成,因此无法指定一个来关闭频道(一条通道只能关闭一次,请参见;和)
你必须“协调”制作人,当所有人都完成他们的工作时,协调人应该关闭频道
消费者应在通道上使用for range
,因为for range
构造在关闭前从通道接收所有发送的值,然后自动终止
对于协调,建议使用。在这种情况下,您是使用全局的还是本地的,然后将其传递给生产者,这取决于您。使用本地的解决方案将使解决方案更通用,更易于扩展。需要注意的是,您必须传递指向sync.WaitGroup
的指针。每当启动新生产者时,请增加nt使用waitgroup。当生产者完成时,它可以发出此使用的信号,最好使用延迟
(因此无论发生什么情况,它都会运行,以缓解异常情况下的死锁)。控制器可以等待所有生产者完成使用
这里有一个完整的解决方案:
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)
}
}
请参阅相关问题:使用两个等待组可以优雅地解决此问题。通过关闭通道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()
}
主程序包
进口(
“fmt”
“同步”
“时间”
)
func producer(ch chan更简单的答案-其中一个制作人需要关闭频道,而消费者只需在频道上移动
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)
}
}
主程序包
进口(
“fmt”
“时间”
)
func制作人(ch chan int,d time.Duration,num int,closer bool){
对于i:=0;i您需要同步goroutine中的所有异步进程。您的主线程和goroutine线程不是同步进程。您的主线程永远不知道何时停止从goroutine调用通道。由于主线程在通道上循环,它总是从通道调用值,并且当goroutine完成并且通道停止发送值,您的主线程无法从通道获取更多的值,因此情况会变成死锁。要避免这种情况,请使用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);
}
}
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);
}
}
主程序包
进口(
“fmt”
“时间”
“同步”
)
func producer(ch chan int,d time.Duration,num int,wg*sync.WaitGroup){
对于i:=0;i您可以解决这个问题,您的问题是您在通道中“发布”整数,然后您尝试从主线程读取它们。然后,当goroutines结束时,整数流停止。(当两个producer
s结束时,没有更多的整数发布到通道中)你仍然在主线程上等待更多的整数被发布,这是永远不会发生的。尝试在频道不再使用后使用close(ch)
关闭频道。我做了close(ch)
你在造成死锁的地方关闭了它。你“在无限for循环中”,正在继续轮询频道,等待向其发送任何整数。在两个生产者结束后,您仍在等待更多,而没有生成任何内容。close(ch)
必须位于生产者的末尾(但在您的情况下,这将很难,因为您有两个生产者)。首先,尝试删除一个制作者,然后运行代码,同样的崩溃也会发生。然后尝试将close(ch)
移动到制作者例程的末尾,将会成功。但是在2个制作者的情况下,您可能需要另一个频道或其他东西,您需要更好的设计。如果不是“closer”的制作者会怎么样当closer关闭通道时仍在运行?这不是一个非常稳定的设计。当然!我给出了这个答案,因为询问者被代码卡住了,这是最简单的解困方法。设计会在稍后进行。但他们几乎会立即被卡住。我完全支持“一次解决一个问题”但是,通过创建不同的问题来解决问题似乎适得其反,特别是当存在sync.WaitGroup
来处理这种情况时。是的,也许吧。有时引入不同的设计太难理解了。无论如何,我编辑了我的答案以澄清目标。我更喜欢合并
功能。您提供的博客ed正在准确地解释fan-in
和fan-out
以及如何处理多个goroutine。顺便说一句,谢谢。这与我的解决方案“完全”相同吗(除了你只调用一次wg.Add()
,并且“错位”了defer wg.Done()
)?我很惊讶我们有相同的解决方案。我曾在某个地方读到有关sync.waitgroup的内容。实际上,我曾经编写过与此稍有不同的解决方案,但我将其更改为与op问题相匹配。我将更改为我自己的解决方案。
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);
}
}
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);
}
}