在workerpool上的通道死锁

时间:2017-05-29 14:26:47

标签: go channel

我通过建立一个1000名工人的工人池来玩弄渠道。目前我收到以下错误:

fatal error: all goroutines are asleep - deadlock!

这是我的代码:

package main

import "fmt"
import "time"


func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("worker", id, "started  job", j)
        time.Sleep(time.Second)
        fmt.Println("worker", id, "finished job", j)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    for w := 1; w <= 1000; w++ {
        go worker(w, jobs, results)
    }

    for j := 1; j < 1000000; j++ {
        jobs <- j
    }
    close(jobs)
    fmt.Println("==========CLOSED==============")

    for i:=0;i<len(results);i++ {
        <-results
    }
}

为什么会这样?我还是新手,我希望能够理解这一点。

4 个答案:

答案 0 :(得分:3)

虽然托马斯的答案基本上是正确的,但我发布了我的版本,这是IMO更好的Go,也适用于无缓冲的频道:

func main() {
    jobs := make(chan int)
    results := make(chan int)

    var wg sync.WaitGroup

    // you could init the WaitGroup's count here with one call but this is error
    // prone - if you change the loop's size you could forget to change the
    // WG's count. So call wg.Add in loop
    //wg.Add(1000)
    for w := 1; w <= 1000; w++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            worker(w, jobs, results)
        }()
    }

    go func() {
        for j := 1; j < 2000; j++ {
            jobs <- j
        }
        close(jobs)
        fmt.Println("==========CLOSED==============")
    }()

    // in this gorutine we wait until all "producer" routines are done
    // then close the results channel so that the consumer loop stops
    go func() {
        wg.Wait()
        close(results)
    }()

    for i := range results {
        fmt.Print(i, " ")
    }
    fmt.Println("==========DONE==============")
}

答案 1 :(得分:1)

以下代码:

    for j := 1; j < 1000000; j++ {
        jobs <- j
    }

应该在一个单独的goroutine中运行,因为所有的工作人员都会阻止等待主gorourine在结果通道上接收,而主要的goroutine会卡在循环中。

答案 2 :(得分:1)

问题在于您的频道正在填满。 main()例程尝试在读取任何结果之前将所有作业放入jobs频道。但是results频道在对频道的任何写入都会阻止之前只有100个结果的空间,所以所有工作人员最终都会阻止等待这个频道中的空间 - 永远不会来的空间,因为main()有尚未开始从results开始阅读。

要快速解决此问题,您可以使jobs足够大以容纳所有作业,因此main()功能可以继续读取阶段;或者你可以使results足够大以容纳所有结果,这样工人就可以输出结果而不会阻塞。

更好的做法是让另一个goroutine填满jobs队列,因此main()可以直接阅读结果:

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    for w := 1; w <= 1000; w++ {
        go worker(w, jobs, results)
    }

    go func() {
        for j := 1; j < 1000000; j++ {
            jobs <- j
        }
        close(jobs)
        fmt.Println("==========CLOSED==============")
    }

    for i := 1; i < 1000000; i++ {
        <-results
    }
}

请注意,我必须将最终的for循环更改为固定的迭代次数,否则它可能会在读取所有结果之前终止。

答案 3 :(得分:0)

package main

import (
    "fmt"
    "sync"
    "time"
)

func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
    defer wg.Done()

    for j := range jobs {
        fmt.Println("worker", id, "started  job", j)
        time.Sleep(time.Millisecond * time.Duration(10))
        fmt.Println("worker", id, "finished job", j)
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    wg := new(sync.WaitGroup)
    wg.Add(1000)

    for w := 1; w <= 1000; w++ {
        go worker(w, jobs, results, wg)
    }

    go func() {
        wg.Wait()
        close(results)
    }()

    go func() {
        for j := 1; j < 1000000; j++ {
            jobs <- j
        }
        close(jobs)
    }()

    sum := 0
    for v := range results {
        sum += v
    }

    fmt.Println("==========CLOSED==============")
    fmt.Println("sum", sum)
}