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語言基礎
代碼倉庫