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語言基礎

代碼倉庫

發佈了47 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章