欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-23 14:21:26
...

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语言基础

代码仓库

相关标签: go