Golang:在waitGroup.Wait()上出现“致命错误:所有goroutine都在睡眠-死锁”

时间:2019-02-21 12:20:03

标签: file go concurrency

我正在尝试编写代码,该代码可以同时读取文件并将内容发布到一个通道。

Here是我的代码和代码的链接:

func main() {
    bufferSize := int64(10)
    f, err := os.Open("tags-c.csv")
    if err != nil {
        panic(err)
    }
    fileinfo, err := f.Stat()
    if err != nil {
        fmt.Println(err)
        return
    }
    filesize := int64(fileinfo.Size())
    fmt.Println(filesize)
    routines := filesize / bufferSize
    if remainder := filesize % bufferSize; remainder != 0 {
        routines++
    }
    fmt.Println("Total routines : ", routines)

    channel := make(chan string, 10)
    wg := &sync.WaitGroup{}

    for i := int64(0); i < int64(routines); i++ {
        wg.Add(1)
        go read(i*bufferSize, f, channel, bufferSize, filesize, wg)

    }
    fmt.Println("waiting")
    wg.Wait()
    fmt.Println("wait over")
    close(channel)

    readChannel(channel)
}

func readChannel(channel chan string) {
    for {
        data, more := <-channel
        if more == false {
            break
        }
        fmt.Print(data)
    }
}

func read(seek int64, file *os.File, channel chan string, bufferSize int64, filesize int64, wg *sync.WaitGroup) {
    defer wg.Done()
    fmt.Println("read :: ", seek)
    var buf []byte
    if filesize < bufferSize {
        buf = make([]byte, filesize)
    } else if (filesize - seek) < bufferSize {
        buf = make([]byte, filesize-seek)
    } else {
        buf = make([]byte, bufferSize)
    }

    n, err := file.ReadAt(buf, seek)
    if err != nil {
        log.Printf("loc %d err: %v", seek, err)
        return
    }
    if n > 0 {
        channel <- string(buf[:n])
        fmt.Println("ret :: ", seek)
    }
}

我尝试在线检查,但令我惊讶的是,我已经照顾了提到的解决方案。任何帮助将不胜感激。

1 个答案:

答案 0 :(得分:2)

问题在于,您希望所有启动的阅读器goroutine在完成操作之前都先完成,然后耗尽它们提供结果的渠道。

通道被缓冲,最多可容纳10个元素。一旦有10个goroutine在其上发送消息,其余的将被阻止,因此它们将永远无法完成(因为从该通道读取只能在它们全部返回后才能开始:这是死锁)。

因此,您应该启动另一个goroutine来与读取器goroutine同时接收结果:

done := make(chan struct{})
go readChannel(channel, done)

fmt.Println("waiting")
wg.Wait()
fmt.Println("wait over")
close(channel)

// Wait for completion of collecting the results:
<-done

读取通道的位置应为for range(在通道关闭且在关闭之前已从通道接收到的所有值都已终止时终止):

func readChannel(channel chan string, done chan struct{}) {
    for data := range channel {
        fmt.Print(data)
    }
    close(done)
}

请注意,我使用了done通道,因此主goroutine也将等待goroutine接收结果。

还要注意,由于在大多数情况下磁盘IO是瓶颈而不是CPU,并且由于从多个goroutine传递和接收结果也有一些开销,因此很可能您看不到任何改进来自多个goroutine的文件。