Goroutines,频道和僵局

时间:2015-09-28 22:05:52

标签: go goroutine channels

我正在尝试更多地了解go的频道和goroutines,所以我决定制作一个小程序来计算文件中的单词,由bufio.NewScanner对象读取:

nCPUs := flag.Int("cpu", 2, "number of CPUs to use")
flag.Parse()
runtime.GOMAXPROCS(*nCPUs)    

scanner := bufio.NewScanner(file)
lines := make(chan string)
results := make(chan int)

for i := 0; i < *nCPUs; i++ {
    go func() {
        for line := range lines {
            fmt.Printf("%s\n", line)
            results <- len(strings.Split(line, " "))
        }
    }()
}

for scanner.Scan(){
    lines <- scanner.Text()
}
close(lines)


acc := 0
for i := range results {
      acc += i
 }

fmt.Printf("%d\n", acc)

现在,在大多数示例中,我发现到目前为止,linesresults个频道都会被缓冲,例如make(chan int, NUMBER_OF_LINES_IN_FILE)。仍然,在运行此代码后,我的程序存在fatal error: all goroutines are asleep - deadlock!错误消息。

基本上我的想法是我需要两个通道:一个是从文件中传递给goroutine的行(因为它可以是任何大小,我不认为我需要通知{的大小{1}}函数调用。另一个通道将从goroutine收集结果,在main函数中我会用它来计算累积结果。

使用goroutines和渠道以这种方式编程的最佳选择是什么?非常感谢任何帮助。

2 个答案:

答案 0 :(得分:6)

正如@AndrewN指出的那样,问题是每个goroutine都试图发送到results频道,但这些发送将阻止,因为results频道是无缓冲的,没有任何内容从它们读取直到for i := range results循环。你永远不会进入那个循环,因为你首先需要完成for scanner.Scan()循环,它试图将所有line发送到lines通道,因为goroutines是永远不会回到range lines,因为他们发送到results

你可以尝试做的第一件事就是将scanner.Scan()内容放入goroutine中,这样就可以立即开始读取results频道。但是,您将遇到的下一个问题是知道何时结束for i := range results循环。您希望在results频道关闭某些内容,但只有在原始goroutine完成后才能读取lines频道。您可以在关闭results频道后立即关闭lines频道,但我认为这可能会引发潜在竞争,所以最安全的做法是等待原始的两个goroutine在关闭之前完成results频道:[(游乐场链接)[https://play.golang.org/p/OnQRT9ie5U]]

package main

import "fmt"
import "runtime"
import "bufio"
import "strings"
import "sync"

func main() {
    runtime.GOMAXPROCS(2)

    scanner := bufio.NewScanner(strings.NewReader(`
hi mom
hi dad
hi sister
goodbye`))
    lines := make(chan string)
    results := make(chan int)

    wg := sync.WaitGroup{}
    for i := 0; i < 2; i++ {
        wg.Add(1)
        go func() {
            for line := range lines {
                fmt.Printf("%s\n", line)
                results <- len(strings.Split(line, " "))
            }
            wg.Done()
        }()
    }

    go func() {
        for scanner.Scan() {
            lines <- scanner.Text()
        }
        close(lines)
        wg.Wait()
        close(results)
    }()

    acc := 0
    for i := range results {
        acc += i
    }

    fmt.Printf("%d\n", acc)
}

答案 1 :(得分:4)

go中的频道由default无缓冲,这意味着您产生的匿名goroutine都不能发送到结果频道,直到您开始尝试从该频道接收为止。在 scanner.Scan()完成填充频道之后,它才开始在主程序中执行...它已被阻止直到您的匿名函数可以发送到结果频道并重新启动它们的循环。死锁。

代码中的另一个问题,即使通过缓冲通道来轻松修复上述问题,只要没有更多结果输入, for i:= range results 也会死锁,由于频道尚未关闭。

编辑:如果你想避免缓冲频道,那么这里有一个潜在的solution。基本上,通过新的goroutine执行发送到结果通道可以避免第一个问题,允许线循环完成。第二个问题(不知道何时停止读取频道)通过计算创建时的goroutines并在考虑每个goroutine时明确关闭频道来避免。与waitgroups做类似的事情可能会更好,但这只是一种非常快速的方式来展示如何做这个无缓冲的。