总是与渠道陷入僵局

Always getting deadlock with channels

我正在学习使用 Go 通道,但总是遇到死锁。这段代码可能有什么问题?当数组大小不相等时,打印机随机停止工作;我想以某种方式通知打印机接收器停止工作会有所帮助。任何想法如何解决它?我的代码粘贴在下面。

package main

import (
    "fmt"
    "sync"
)

var wg = sync.WaitGroup{}
var wgs = sync.WaitGroup{}
var sg = make(chan int, 50)
var gp1 = make(chan int, 50)
var gp2 = make(chan int, 50)

func main(){
    wgs.Add(2)
    go Sender(0)
    go Sender(11)

    wg.Add(3)
    go Receiver()

    go Printer()
    go Printer2()

    wg.Wait()
}

func Sender(start int){
    defer wgs.Done()
    for i := start; i < 20; i++ {
        sg <- i
    }
}

func Receiver(){
    defer wg.Done()
    for i := 0; i < 20; i++{
        nr := <- sg
        if nr % 2 == 0{
            gp1 <- nr
        } else{
            gp2 <- nr
        }
    }
}

func Printer(){
    defer wg.Done()
    var m [10]int

    for i := 0; i < 10; i++ {
        m[i] = <- gp1
    }

    wgs.Wait()
    fmt.Println(m)
}

func Printer2(){
    defer wg.Done()
    var m [10]int

    for i := 0; i < 10; i++ {
        m[i] = <- gp2
    }

    wgs.Wait()

    fmt.Println(m)
}
// Better to use this one
// func Receiver(senderChannel <-chan int, printerChannel1 chan<- int, printerChannel2 chan<- int, wg *sync.WaitGroup) {

发件人生成(我认为是 28 条消息)。其中前 20 个中大约有一半转到 gp1 和 gp2 之一。然后打印机和打印机 2 卸载消息

麻烦的是,Receiver 拆分消息的方式取决于收到的数字是奇数还是偶数。但是你不能控制这个。如果其中一台打印机的队列中的项目少于 10 个,它将挂起

这是一个潜在问题

你的核心问题是这里的一切都是"dead reckoning":他们希望看到固定数量的消息,但这不一定与现实相符。您应该设置通道,以便在生成所有数据后关闭它们。

这可能意味着设置一个中间函数来管理发送:

func Sender(from, to int, c chan<- int) {
    for i := from; i < to; i++ {
        c <- i
    }
}

func SendEverything(c chan<- int) {
    var wg sync.WaitGroup
    wg.Add(2)
    go func() {
        defer wg.Done()
        Sender(0, 20, c)
    }()
    go func() {
        defer wg.Done()
        Sender(11, 20, c)
    }()
    wg.Wait()
    close(c)
}

使调度程序功能适用于频道中的所有内容:

func Receive(c <-chan int, odds, evens chan<- int) {
    for n := range c {
        if n%2 == 0 {
            evens <- n
        } else {
            odds <- n
        }
    }
    close(odds)
    close(evens)
}

然后可以共享一个打印函数:

func Printer(prefix string, c <-chan int) {
    for n := range c {
        fmt.Printf("%s: %d\n", prefix, n)
    }
}

最后,你有一个将它们拼接在一起的主要功能:

func main() {
    var wg sync.WaitGroup
    inputs := make(chan int)
    odds := make(chan int)
    evens := make(chan int)

    wg.Add(4)
    go func() {
        defer wg.Done()
        SendEverything(inputs)
    }()
    go func() {
        defer wg.Done()
        Receive(inputs, odds, evens)
    }()
    go func() {
        defer wg.Done()
        Printer("odd number", odds)
    }()
    go func() {
        defer wg.Done()
        Printer("even number", evens)
    }()
    wg.Wait()
}

完整示例位于 https://play.golang.org/p/qTUqlt-uaWH

请注意,我已完全避免使用任何全局变量,并且任何事物都有一个希望不言自明的非常短的名称(in 是简单的整数,c 是一个频道)或者是完整的单词(oddsevens)。我倾向于将 sync.WaitGroup 对象保留在创建它们的地方。由于一切都作为参数传递,我不需要同一个函数的两个副本来作用于不同的全局变量,如果我选择为此编写测试代码,我可以创建我自己的本地频道。