解决goroutines死锁

解决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

我一直在尝试解决我在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)
}
主程序包
进口(
“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);
    }
}