dongmu4591 2018-07-12 10:30
浏览 48
已采纳

如何在Go中使用频道递归列出文件?

I'm trying to use channels to list directory trees recursively.

Currently I get a list of few files and then it gets stuck on one directory. Directory is sent to a worker but it doesn't process it.

How the directory should be sent inside the worker (if file.IsDir()) so that it gets properly processed and also notify the file lister that there are no new files to be processed after recursion is done?

Here's my current attempt:

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "errors"
    "log"
)

// Job for worker
type workerJob struct {
    Root string
}

// Result of a worker
type workerResult struct {
    Filename string
}

func worker(jobs chan workerJob, results chan<- workerResult, done chan bool) {
    for j := range jobs {
        log.Printf(`Directory: %#v`, j.Root)

        dir, err := os.Open(j.Root)

        if err != nil {
            if os.IsPermission(err) {
                // Skip if there's no permission
                continue
            }
            continue
        }

        fInfo, err := dir.Readdir(-1)
        dir.Close()
        if err != nil {
            if os.IsPermission(err) {
                // Skip if there's no permission
                continue
            }
            continue
        }

        for _, file := range fInfo {
            fpath := filepath.Join(dir.Name(), file.Name())

            if file.Mode().IsRegular() {
                // is file
                fs := uint64(file.Size())
                if fs == 0 {
                    // Skip zero sized
                    continue
                }

                r := workerResult{
                    Filename: fpath,
                }

                log.Printf(`sent result: %#v`, r.Filename)
                results <- r
            } else if file.IsDir() {
                // Send directory to be processed by the worker
                nj := workerJob{
                    Root: fpath,
                }
                log.Printf(`sent new dir job: %#v`, nj.Root)
                jobs <- nj
            }
        }

        done <- true
    }
}

func main() {
    dir := `/tmp`

    workerCount := 1

    jobs := make(chan workerJob, workerCount)
    results := make(chan workerResult)
    readDone := make(chan bool)

    // start N workers
    for i := 0; i < workerCount; i++ {
        go worker(jobs, results, readDone)
    }

    jobs <- workerJob{
        Root: dir,
    }

    readloop:
    for {
        select {
        case res := <-results:
            log.Printf(`result=%#v`, res.Filename)
        case _ = <-readDone:
            log.Printf(`got stop`)
            break readloop
        }
    }

}

This results in:

2018/07/12 14:37:29 Directory: "/tmp"
2018/07/12 14:37:29 sent result: "/tmp/.bashrc"
2018/07/12 14:37:29 result="/tmp/.bashrc"
2018/07/12 14:37:29 sent result: "/tmp/.bash_profile"
2018/07/12 14:37:29 result="/tmp/.bash_profile"
2018/07/12 14:37:29 sent result: "/tmp/.bash_logout"
2018/07/12 14:37:29 result="/tmp/.bash_logout"
2018/07/12 14:37:29 sent result: "/tmp/.xinitrc"
2018/07/12 14:37:29 result="/tmp/.xinitrc"
2018/07/12 14:37:29 sent new dir job: "/tmp/.config"
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select]:
main.main()
    +0x281

goroutine 5 [chan send]:
main.worker(0xc42005a060, 0xc420078060, 0xc4200780c0)
    +0x4e7
created by main.main
    +0x109

Process finished with exit code 2

How the deadlock can be fixed?

  • 写回答

2条回答 默认 最新

  • duanmen1887 2018-07-12 15:12
    关注

    You have noticed that jobs <- nj hangs forever. This is because that the operation blocks until a worker receives in the range loop, and as long as it blocks there, it cannot reach the range loop.

    To solve the problem you spawn a new goroutine to do that.

    go func() {
            jobs <- nj
    }()
    

    And there is one more problem: your readDone channel.

    Currently that channel will be emitted every time your worker finishes a job, and that leads to the possibility (select picks ready channel randomly) that select in func main() picks it up and then shutdown the system, which makes all remaining job and result lost.

    To solve this part of the problem, you should use a sync.WaitGroup. Everytime you adds a new job, you call wg.Add(1) and everytime your worker finishes a job, you call wg.Done(). In func main(), you shall spawn a goroutine that use wg.Wait() to wait all jobs to finish and then shutdown the system using readDone.

    // One initial job
    wg.Add(1)
    go func() {
        jobs <- workerJob{
            Root: dir,
        }
    }()
    
    // When all jobs finished, shutdown the system.
    go func() {
        wg.Wait()
        readDone <- true
    }()
    

    Full code: https://play.golang.org/p/KzVxtflu1eU

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 Oracle中如何从clob类型截取特定字符串后面的字符
  • ¥15 想通过pywinauto自动电机应用程序按钮,但是找不到应用程序按钮信息
  • ¥15 MATLAB中streamslice问题
  • ¥15 如何在炒股软件中,爬到我想看的日k线
  • ¥15 51单片机中C语言怎么做到下面类似的功能的函数(相关搜索:c语言)
  • ¥15 seatunnel 怎么配置Elasticsearch
  • ¥15 PSCAD安装问题 ERROR: Visual Studio 2013, 2015, 2017 or 2019 is not found in the system.
  • ¥15 (标签-MATLAB|关键词-多址)
  • ¥15 关于#MATLAB#的问题,如何解决?(相关搜索:信噪比,系统容量)
  • ¥500 52810做蓝牙接受端