Housie计划中的僵局。生产者 - 消费者模式

时间:2015-04-11 01:52:37

标签: go deadlock channel goroutine

我正在尝试实施一个housie游戏,其中goroutine产生数字,另外3个goroutines检查它们是否在他们的标记中并通知制作者是否所有数字都已生成。我已经通过以下方式在golang中实现了它。这导致死锁。知道为什么会这样吗?这是一个“作业问题”,我只是在实现它去学习更好。

package main

import (
    "fmt"
    "math/rand"
)

type PersonID int

func contains(s []int, e int) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

func Person(called_number chan int, claim_prize chan PersonID, received chan bool, coupon []int, person_id PersonID) {
    numFound := 0
    for i := 0; i < len(coupon); i++ {
        current_number := <-called_number
        found := contains(coupon, current_number)
        if found {
            numFound++
        }
        if numFound == len(coupon) {
            claim_prize <- person_id
        } else {
            received <- true
        }
    }
}

func main() {
    var called_number chan int
    var claim_prize chan PersonID
    var received chan bool

    tokens := make([][]int, 3)
    for i := 0; i < 3; i++ {
        tokens[i] = make([]int, 12)
        for j := 0; j < 12; j++ {
            num := rand.Intn(100) + 1
            found := contains(tokens[i], num)
            for found {
                num = rand.Intn(100) + 1
                found = contains(tokens[i], num)
            }
            tokens[i][j] = num
        }
    }

    go Person(called_number, claim_prize, received, tokens[0], 0)
    go Person(called_number, claim_prize, received, tokens[1], 1)
    go Person(called_number, claim_prize, received, tokens[2], 2)

    claimants := make([]PersonID, 0)
    prev_called := make(map[int]bool)
    for i := 0; i < 100; i++ {
        if len(claimants) == 3 {
            break
        }
        num := rand.Intn(100) + 1
        _, ok := prev_called[num]
        for ok {
            num = rand.Intn(100) + 1
            _, ok = prev_called[num]
        }
        prev_called[num] = true
        called_number <- num
        for j := 0; j < 3; j++ {
            select {
            case _ = <-received:
                continue
            case pid := <-claim_prize:
                claimants = append(claimants, pid)
            }
        }
    }

    fmt.Println(claimants)
}

编辑: 确切的问题是生产者需要将数字发送给每个消费者。当消费者收到其中的所有号码时,它可以领取奖品。根据@OneOfOne的说法,我对程序进行了一些更改。变化是现在每个消费者都有一个单独的渠道,我在申请奖金后关闭它。下面是新程序,它仍然是死锁。

package main

import (
    "fmt"
    "math/rand"
)

func contains(s []int, e int) bool {
    for _, a := range s {
        if a == e {
            return true
        }
    }
    return false
}

func Person(called_number chan int, claim_prize chan int, received chan bool, coupon []int, person_id int) {
    numFound := 0
    for current_number := range called_number {
        if contains(coupon, current_number) {
            numFound++
        }
        if numFound == len(coupon) {
            fmt.Println(person_id)
            claim_prize <- person_id
        } else {
            received <- true
        }
    }
}

func main() {
    var (
        called_number1 = make(chan int, 1)
        called_number2 = make(chan int, 1)
        called_number3 = make(chan int, 1)
        claim_prize    = make(chan int, 1)
        received       = make(chan bool, 1)
    )

    tokens := make([][]int, 3)
    for i := 0; i < 3; i++ {
        tokens[i] = make([]int, 12)
        for j := 0; j < 12; j++ {
            num := rand.Intn(100) + 1
            found := contains(tokens[i], num)
            for found {
                num = rand.Intn(100) + 1
                found = contains(tokens[i], num)
            }
            tokens[i][j] = num
        }
    }

    go Person(called_number1, claim_prize, received, tokens[0], 0)
    go Person(called_number2, claim_prize, received, tokens[1], 1)
    go Person(called_number3, claim_prize, received, tokens[2], 2)

    claimants := make([]int, 0)
    prev_called := make(map[int]bool)
    for i := 0; i < 100; i++ {
        if len(claimants) == 3 {
            break
        }
        num := rand.Intn(100) + 1
        _, ok := prev_called[num]
        for ok {
            num = rand.Intn(100) + 1
            _, ok = prev_called[num]
        }
        prev_called[num] = true
        if !contains(claimants, 0) {
            called_number1 <- num
        }
        if !contains(claimants, 1) {
            called_number2 <- num
        }
        if !contains(claimants, 2) {
            called_number3 <- num
        }
        for j := 0; j < 3; j++ {
            select {
            case _ = <-received:
                continue
            case pid := <-claim_prize:
                if pid == 0 { close(called_number1) }
                if pid == 1 { close(called_number2) }
                if pid == 2 { close(called_number3) }
                claimants = append(claimants, pid)
            }
        }
    }
    fmt.Println(claimants)
}

EDIT2:这仍然陷入僵局,因为即使在goroutine完成后我也没有减少等待的频道数量。这一切都行得通。

1 个答案:

答案 0 :(得分:2)

几个问题:

  1. 你正在使用一个nil频道,所以它只是永远阻止,因为某些原因它没有慌张。

  2. 此外,您的第二个内部循环将无限期阻止,因为它等待阅读但没有发送任何内容。

  3. Person循环结束后,called_number <- num将永久阻止。

  4. //编辑 有点工作代码:http://play.golang.org/p/3At5nuJTuk

    但逻辑是有缺陷的,你必须重新考虑它。