Go 语言圣经 8.8 示例: 并发的目录遍历

8.8 示例: 并发的目录遍历

知识点

  • 1.利用并发遍历并计算文件大小
  • 2.利用select,优化打印文件大小
  • 3.利用channel设置最大信号量,来防止打开文件过多

代码

func test_concurrent_directory()  {

    // Determine the initial directories.
    //------33333计算大小
    roots := []string{"/Users"}
    if len(roots) == 0 {
        roots = []string{"."}
    }
    // Traverse the file tree.
    fileSizes := make(chan int64)
    //go func() {
    //  for _, root := range roots {
    //      walkDir(root, fileSizes)
    //  }
    //  close(fileSizes)
    //}()

    //------44444计算大小优化
    /*
        因为磁盘系统并行限制,为了优化
        使用sync.WaitGroup (§8.5)来对仍旧活跃的walkDir调用进行计数,
        另一个goroutine会在计数器减为零的时候将fileSizes这个channel关闭
    */
    var n sync.WaitGroup
    for _, root := range roots {
        n.Add(1)
        go walkDir_group(root, &n, fileSizes)
    }
    go func() {
        n.Wait()
        close(fileSizes)
    }()

    // Print the results.
    var nfiles, nbytes int64

    //------11111累加大小打印
    //for size := range fileSizes {
    //  nfiles++
    //  nbytes += size
    //}
    //printDiskUsage(nfiles, nbytes)

    //------22222累加大小打印优化
    /*
        主goroutine现在使用了计时器来每500ms生成事件,
        然后用select语句来等待文件大小的消息来更新总大小数据,
        或者一个计时器的事件来打印当前的总大小数据
    */
    // Print the results periodically.
    var tick <-chan time.Time
    tick = time.Tick(500 * time.Millisecond)
loop:
    for {
        select {
        case size, ok := <-fileSizes:
            if !ok {
                break loop // fileSizes was closed
            }
            nfiles++
            nbytes += size
        case <-tick:
            printDiskUsage(nfiles, nbytes)
        }
    }
    printDiskUsage(nfiles, nbytes) // final totals
}
/*
    练习 8.9: 编写一个du工具,每隔一段时间将root目录下的目录大小计算并显示出来
*/
func test_exerise89(timeS time.Duration, filep string)  {

    for {
        /*
            这一章,其实已经为我们写好了这个练习,
            只需要吧最优化的部分拿出来即可
        */
        roots := []string{filep}
        if len(roots) == 0 {
            roots = []string{"."}
        }

        // Traverse the file tree.
        fileSizes := make(chan int64)

        var n sync.WaitGroup
        for _, root := range roots {
            n.Add(1)
            go walkDir_group(root, &n, fileSizes)
        }
        go func() {
            n.Wait()
            close(fileSizes)
        }()

        var nfiles, nbytes int64
        for size := range fileSizes {
            nfiles++
            nbytes += size
        }

        //root目录下的目录大小计算并显示出来
        fmt.Println(filep + "  fileSizes")
        printDiskUsage(nfiles, nbytes)

        //每隔一段时间
        time.Sleep(timeS)
    }
}

func printDiskUsage(nfiles, nbytes int64) {
    fmt.Printf("%d files  %.1f GB\n", nfiles, float64(nbytes)/1e9)
}
func walkDir(dir string, fileSizes chan<- int64) {
    for _, entry := range dirents(dir) {
        if entry.IsDir() {
            subdir := filepath.Join(dir, entry.Name())
            walkDir(subdir, fileSizes)
        } else {
            fileSizes <- entry.Size()
        }
    }
}
func walkDir_group(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
    defer n.Done()
    for _, entry := range dirents(dir) {
        if entry.IsDir() {
            n.Add(1)
            subdir := filepath.Join(dir, entry.Name())
            go walkDir_group(subdir, n, fileSizes)
        } else {
            fileSizes <- entry.Size()
        }
    }
}
/*
    由于这个程序在高峰期会创建成百上千的goroutine,
    我们需要修改dirents函数,
    用计数信号量来阻止他同时打开太多的文件
*/
// sema is a counting semaphore for limiting concurrency in dirents.
var sema = make(chan struct{}, 20)
func dirents(dir string) []os.FileInfo {
    sema <- struct{}{}        // acquire token
    defer func() { <-sema }() // release token
    entries, err := ioutil.ReadDir(dir)
    if err != nil {
        fmt.Fprintf(os.Stderr, "du1: %v\n", err)
        return nil
    }
    return entries
}

备注

《Go 语言圣经》

  • 学习记录所使用的GO版本是1.8
  • 学习记录所使用的编译器工具为GoLand
  • 学习记录所使用的系统环境为Mac os
  • 学习者有一定的C语言基础

代码仓库

猜你喜欢

转载自blog.csdn.net/liushihua147/article/details/80662078