即使频道被关闭,常规也会陷入僵局

时间:2014-09-19 12:49:31

标签: concurrency go goroutine

我有一个列表,其中包含一个弹出元素的函数,另一个函数是"接收"弹出的元素。我认为接收器关闭后会关闭通道,但似乎该程序在到达之前已经死锁。这是最好的方法吗?我应该有另一个通道来检测弹出窗口何时完成?

Playground link

func pop(list *[]int, c chan int) {
    if len(*list) != 0 {
        result := (*list)[0]
        *list = (*list)[1:]
        fmt.Println("about to send ", result)
        c <- result
    } else {
        return
    }
}

func receiver(c chan int) {

    result := <-c
    fmt.Println("received ", result)
}

var list = []int{1, 2, 3}

func main() {

    fmt.Println("Main")
    c := make(chan int)
    go pop(&list, c)
    go pop(&list, c)
    for len(list) > 0 {
        receiver(c)
    }
    close(c) //Dosen't seem to have any effect
    fmt.Println("done")

}

1 个答案:

答案 0 :(得分:6)

代码存在很多问题,让我们看看。

  1. 你的pop函数在访问切片时没有锁定,所以这就是数据竞争。
  2. for len(list) > 0 {}是一个数据竞赛,因为您正在访问列表,同时在其他2个goroutine中修改它。
  3. for len(list) > 0 {}将永远不会返回,因为您的列表中有3个项目,但您只拨打两次。
  4. 由于#3,
  5. receiver(c)错误,它试图从频道中读取但没有任何内容写入。
  6. 一种方法是使用一个作家(pop)和多个读者(receiver):

    func pop(list *[]int, c chan int, done chan bool) {
        for len(*list) != 0 {
            result := (*list)[0]
            *list = (*list)[1:]
            fmt.Println("about to send ", result)
            c <- result
        }
        close(c)
        done <- true
    }
    
    func receiver(c chan int) {
        for result := range c {
            fmt.Println("received ", result)
        }
    }
    
    var list = []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    func main() {
        c := make(chan int)
        done := make(chan bool)
        go pop(&list, c, done)
        go receiver(c)
        go receiver(c)
        go receiver(c)
        <-done
        fmt.Println("done")
    }
    

    playground

    在弄乱goroutines时总是使用go run -race blah.go