多个生产者,一个消费者:所有goroutine都处于睡眠状态-死锁

时间:2019-11-10 22:12:53

标签: go

在执行工作之前,我一直遵循一种检查通道中是否有东西的模式:

func consume(msg <-chan message) {
  for {
    if m, ok := <-msg; ok {
      fmt.Println("More messages:", m)
    } else {
      break
    }
  }
}
基于此video

。这是我的完整代码:

package main

import (
    "fmt"
    "strconv"
    "strings"
    "sync"
)

type message struct {
    body string
    code int
}

var markets []string = []string{"BTC", "ETH", "LTC"}

// produces messages into the chan
func produce(n int, market string, msg chan<- message, wg *sync.WaitGroup) {
    // for i := 0; i < n; i++ {
    var msgToSend = message{
        body: strings.Join([]string{"market: ", market, ", #", strconv.Itoa(1)}, ""),
        code: 1,
    }
    fmt.Println("Producing:", msgToSend)
    msg <- msgToSend
    // }
    wg.Done()
}

func receive(msg <-chan message, wg *sync.WaitGroup) {
    for {
        if m, ok := <-msg; ok {
            fmt.Println("Received:", m)
        } else {
            fmt.Println("Breaking from receiving")
            break
        }
    }
    wg.Done()
}

func main() {
    wg := sync.WaitGroup{}
    msgC := make(chan message, 100)
    defer func() {
        close(msgC)
    }()
    for ix, market := range markets {
        wg.Add(1)
        go produce(ix+1, market, msgC, &wg)
    }
    wg.Add(1)
    go receive(msgC, &wg)
    wg.Wait()
}

如果您尝试运行它,则在打印将要中断的消息之前,我们最终将陷入僵局。自上次以来,当chan中没有其他内容时,tbh才有意义,因此我们试图拉出该值,因此出现此错误。但是,此模式if m, ok := <- msg; ok不可行。如何使此代码正常工作以及为什么会出现此死锁错误(大概该模式应该起作用?)。

3 个答案:

答案 0 :(得分:1)

鉴于您在单个频道上确实有多个作者,因此您会遇到一些挑战,因为在Go中执行此操作的简单方法通常是在单个频道上拥有一个作者,然后再拥有一个作者编写者在发送最后一个数据后关闭通道:

func produce(... args including channel) {
    defer close(ch)
    for stuff_to_produce {
        ch <- item
    }
}

此模式具有很好的属性,无论您如何离开produce,渠道都会关闭,这标志着生产结束。

您没有使用此模式-您向多个goroutine提供一个通道,每个goroutine可以发送一个消息-因此您需要移动close(当然,也可以移动,请使用其他模式)。表达所需模式的最简单方法是:

func overall_produce(... args including channel ...) {
    var pg sync.WaitGroup
    defer close(ch)
    for stuff_to_produce {
        pg.Add(1)
        go produceInParallel(ch, &pg) // add more args if appropriate
    }
    pg.Wait()
}

pg计数器会累积活跃的生产者。每个人都必须调用pg.Done()来表明它是使用ch完成的。现在,整个制作人都在等待所有操作完成,然后 it 在退出频道时将其关闭。

(如果您将内部produceInParallel函数作为闭包编写,则无需显式传递chpg。也可以编写overallProducer作为关闭。)

请注意,使用for ... range构造可以最好地表达单个消费者的循环:

func receive(msg <-chan message, wg *sync.WaitGroup) {
    for m := range msg {
        fmt.Println("Received:", m)
    }
    wg.Done()
}

(您提到了向循环中添加select的意图,以便在消息尚未准备好时可以执行其他一些计算。如果该代码无法分解为独立的goroutine,则实际上需要更高级的m, ok := <-msg构造。)

还请注意,wg的{​​{1}}(根据您构造其他事物的方式可能是不必要的)与等待组receive完全独立,生产者。诚然,按照书面说明,只有在所有生产者都完成之后才能完成消费者的工作,但我们希望独立等待生产者完成,以便我们可以关闭整体生产者包装中的渠道。

答案 1 :(得分:0)

尝试此代码,我做了一些修复使其有效:

package main

import (
    "fmt"
    "strconv"
    "strings"
    "sync"
)

type message struct {
    body string
    code int
}

var markets []string = []string{"BTC", "ETH", "LTC"}

// produces messages into the chan
func produce(n int, market string, msg chan<- message, wg *sync.WaitGroup) {
    // for i := 0; i < n; i++ {
    var msgToSend = message{
        body: strings.Join([]string{"market: ", market, ", #", strconv.Itoa(1)}, ""),
        code: 1,
    }
    fmt.Println("Producing:", msgToSend)
    msg <- msgToSend
    // }

}

func receive(msg <-chan message, wg *sync.WaitGroup) {
    for {
        if m, ok := <-msg; ok {
            fmt.Println("Received:", m)
            wg.Done()
        }
    }
}

func consume(msg <-chan message) {
  for {
    if m, ok := <-msg; ok {
      fmt.Println("More messages:", m)
    } else {
      break
    }
  }
}

func main() {
    wg := sync.WaitGroup{}
    msgC := make(chan message, 100)
    defer func() {
        close(msgC)
    }()
    for ix, market := range markets {
        wg.Add(1)
        go produce(ix+1, market, msgC, &wg)
    }

    go receive(msgC, &wg)
    wg.Wait()
    fmt.Println("Breaking from receiving")
}

答案 2 :(得分:0)

仅当main返回时,您才能close(msgC),但是与此同时receive正在等待close信号,这就是发生死锁的原因。产生消息后,关闭频道。

package main

import (
    "fmt"
    "strconv"
    "strings"
    "sync"
)

type message struct {
    body string
    code int
}

var markets []string = []string{"BTC", "ETH", "LTC"}

// produces messages into the chan
func produce(n int, market string, msg chan<- message, wg *sync.WaitGroup) {
    // for i := 0; i < n; i++ {
    var msgToSend = message{
        body: strings.Join([]string{"market: ", market, ", #", strconv.Itoa(1)}, ""),
        code: 1,
    }
    fmt.Println("Producing:", msgToSend)
    msg <- msgToSend
    // }
    wg.Done()
}

func receive(msg <-chan message, wg *sync.WaitGroup) {
    for {
        if m, ok := <-msg; ok {
            fmt.Println("Received:", m)
        } else {
            fmt.Println("Breaking from receiving")
            break
        }
    }
    wg.Done()
}

func main() {
    wg := sync.WaitGroup{}
    msgC := make(chan message, 100)
    // defer func() {
    //  close(msgC)
    // }()
    for ix, market := range markets {
        wg.Add(1)
        go produce(ix+1, market, msgC, &wg)
    }
    wg.Wait() // wait for producer
    close(msgC)
    wg.Add(1)
    go receive(msgC, &wg)
    wg.Wait()
}