问题 所有的goroutines都睡着了 - 僵局


对于我的一个要求,我必须创建N个工作进程例程,这将由一个监视例程监视。当所有工作程序例程完成时,监视例程必须结束。我的代码以死锁结束,请帮助。

import "fmt"
import "sync"
import "strconv"

func worker(wg *sync.WaitGroup, cs chan string, i int ){
    defer wg.Done()
    cs<-"worker"+strconv.Itoa(i)    
}

func monitorWorker(wg *sync.WaitGroup, cs chan string) {
    defer wg.Done()
    for i:= range cs {
            fmt.Println(i)
     }
}
func main() {
    wg := &sync.WaitGroup{}
    cs := make(chan string)

    for i:=0;i<10;i++{
             wg.Add(1)
             go worker(wg,cs,i)
    } 

    wg.Add(1)
    go monitorWorker(wg,cs)
    wg.Wait()
}

6399
2017-11-10 17:29


起源



答案:


您的monitorWorker永远不会死。当所有工人完成后,它继续等待cs。这种僵局是因为没有别的东西会发送到cs,因此wg永远不会达到0.一个可能的解决方法是让监视器在所有工人完成时关闭通道。如果for循环在main中,它将结束循环,从main返回,并结束程序。

例如: http://play.golang.org/p/nai7XtTMfr

package main

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

func worker(wg *sync.WaitGroup, cs chan string, i int) {
    defer wg.Done()
    cs <- "worker" + strconv.Itoa(i)
}

func monitorWorker(wg *sync.WaitGroup, cs chan string) {
    wg.Wait()
    close(cs)
}

func main() {
    wg := &sync.WaitGroup{}
    cs := make(chan string)

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go worker(wg, cs, i)
    }

    go monitorWorker(wg, cs)

    for i := range cs {
        fmt.Println(i)

    }
}

编辑: 这是对OP的第一个评论的回答。

您的程序有三个部分需要同步。首先,您的所有员工都需要发送数据。然后您的打印循环需要打印该数据。然后你的主要功能需要返回,从而结束程序。在您的示例中,所有工作人员都发送数据,所有数据都会打印出来,但邮件永远不会发送给main,它应该正常返回。

在我的例子中,main执行打印,“monitorWorker”只是在收到需要打​​印的每个数据时告诉main。这样程序优雅地结束而不是死锁。

如果你坚持将打印循环放在另一个goroutine中,你可以这样做。但是需要将额外的通信发送到main,以便返回。在下一个示例中,我使用通道来确保打印所有数据时的主要结束。

package main

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

func worker(wg *sync.WaitGroup, cs chan string, i int) {
    defer wg.Done()
    cs <- "worker" + strconv.Itoa(i)
}

func monitorWorker(wg *sync.WaitGroup, cs chan string) {
    wg.Wait()
    close(cs)
}

func printWorker(cs <-chan string, done chan<- bool) {
    for i := range cs {
        fmt.Println(i)
    }

    done <- true
}

func main() {
    wg := &sync.WaitGroup{}
    cs := make(chan string)

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go worker(wg, cs, i)
    }

    go monitorWorker(wg, cs)

    done := make(chan bool, 1)
    go printWorker(cs, done)
    <-done
}

10
2017-11-10 17:52



谢谢您的帮助。但这并没有解决我的并行处理问题。在我的情况下,monitorWorker必须并行工作(即从通道打印)。 - vrbilgi
@vrbilgi,我在答案中评论道。 - Stephen Weinberg
我尝试了不同的变化,但你的答案简单而优雅。谢谢:D - vrbilgi


答案:


您的monitorWorker永远不会死。当所有工人完成后,它继续等待cs。这种僵局是因为没有别的东西会发送到cs,因此wg永远不会达到0.一个可能的解决方法是让监视器在所有工人完成时关闭通道。如果for循环在main中,它将结束循环,从main返回,并结束程序。

例如: http://play.golang.org/p/nai7XtTMfr

package main

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

func worker(wg *sync.WaitGroup, cs chan string, i int) {
    defer wg.Done()
    cs <- "worker" + strconv.Itoa(i)
}

func monitorWorker(wg *sync.WaitGroup, cs chan string) {
    wg.Wait()
    close(cs)
}

func main() {
    wg := &sync.WaitGroup{}
    cs := make(chan string)

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go worker(wg, cs, i)
    }

    go monitorWorker(wg, cs)

    for i := range cs {
        fmt.Println(i)

    }
}

编辑: 这是对OP的第一个评论的回答。

您的程序有三个部分需要同步。首先,您的所有员工都需要发送数据。然后您的打印循环需要打印该数据。然后你的主要功能需要返回,从而结束程序。在您的示例中,所有工作人员都发送数据,所有数据都会打印出来,但邮件永远不会发送给main,它应该正常返回。

在我的例子中,main执行打印,“monitorWorker”只是在收到需要打​​印的每个数据时告诉main。这样程序优雅地结束而不是死锁。

如果你坚持将打印循环放在另一个goroutine中,你可以这样做。但是需要将额外的通信发送到main,以便返回。在下一个示例中,我使用通道来确保打印所有数据时的主要结束。

package main

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

func worker(wg *sync.WaitGroup, cs chan string, i int) {
    defer wg.Done()
    cs <- "worker" + strconv.Itoa(i)
}

func monitorWorker(wg *sync.WaitGroup, cs chan string) {
    wg.Wait()
    close(cs)
}

func printWorker(cs <-chan string, done chan<- bool) {
    for i := range cs {
        fmt.Println(i)
    }

    done <- true
}

func main() {
    wg := &sync.WaitGroup{}
    cs := make(chan string)

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go worker(wg, cs, i)
    }

    go monitorWorker(wg, cs)

    done := make(chan bool, 1)
    go printWorker(cs, done)
    <-done
}

10
2017-11-10 17:52



谢谢您的帮助。但这并没有解决我的并行处理问题。在我的情况下,monitorWorker必须并行工作(即从通道打印)。 - vrbilgi
@vrbilgi,我在答案中评论道。 - Stephen Weinberg
我尝试了不同的变化,但你的答案简单而优雅。谢谢:D - vrbilgi