Go语言圣经 - 第8章 Goroutines 和 Channels - 8.8 示例:并发的目录遍历

第8章 Goroutines 和 Channels

Go语言中的并发程序可以用两种手段来实现:goroutine 和 channel,其支持顺序通信进程,或被简称为CSP,CSP是一种并发编程模型,在这种并发编程模型中,值会在不同运行实例中传递,第二个手段便是多线程共享内存

8.8 示例:并发的目录遍历

在本小节中,我们来写一个程序,生成指定目录的硬盘使用情况报告,这个程序和Unix里的du工具比较相似。大多数工作用下面的walkDir函数来完成,这个函数使用dirents函数来枚举一个目录下的所有入口

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 dirents(dir string) []os.FileInfo { 
	entries, err := ioutil.ReadDir(dir) // 返回值类型([]fs.FileInfo, error)
	if err != nil {
		fmt.Fprintf(os.Stderr, "du1:%v\n", err)
		return nil
	}
	return entries
}

我们的大体思路就是给函数一个字符串输入(文件路径),让程序返回其使用情况,由于某个路径中的文件可能是开枝散叶的,这里我们的数据类型使用的是切片,逻辑结构是递归调用

看上面的函数,我们先通过ioutil.ReadDir获得了一个slice,if语句是对错误的处理,这里比较巧妙的,第一个slice中元素是fs.FileInfo,而遍历后的slice类型是os.FileInfo,它是fs.FileInfo 的实例,而FileInfo是一个接口

最后我们执行了递归调用,指导所有的路径都被访问完,然后打印出路径中的文件大小

我们在main函数中使用goroutine来实现一下

func main() {
	flag.Parse()
	roots := flag.Args()
	if len(roots) == 0 {
		roots = []string{"."}
	}

	fileSizes := make(chan int64)
	go func() {
		for _, root := range roots {
			walkDir(root, fileSizes)
		}
		close(fileSizes)
	}()

	var nfiles, nbytes int64
	for size := range fileSizes {
		nfiles++
		nbytes += size
	}
	printDiskUsage(nfiles, nbytes)
}
func printDiskUsage(nfiles, nbytes int64) {
	fmt.Printf("%d files %.1f GB\n", nfiles, float64((nbytes)/1e9))
}

我们先对输入的路径做个基本的解析,确保它是有效路径,然后放入我们要访问的函数中,再以我们想要的格式输出这些信息

xxx@MacBook-Pro-10 ~ % go build /Users/qinjianquan/go/src/awesomeProject/src/chp8/du1.go
xxx@MacBook-Pro-10 ~ % ./du1 $HOME /Users/qinjianquan/go/src/awesomeProject/src
658523 files 117.0 GB

输出上面这些信息需要很长时间,我们希望获得实时进度

但是如果简单的把printDiskUsage函数移入上面的循环,那会打印出太多内容,所以我们不会这么做,我们会设定一个500ms的时间间隔,间歇性的打印,这样既可以获取输出进度又不至于收到太多输出信息,事实上我们并不需要非常细致的指导每时每刻的访问情况

var verbose = flag.Bool("v", false, "show verbose progress messages")
func main() {
	flag.Parse()
	roots := flag.Args()
	if len(roots) == 0 {
		roots = []string{"."}
	}

	fileSizes := make(chan int64)

	go func() {
		for _, root := range roots {
			walkDir(root, fileSizes)
		}
		close(fileSizes)
	}()

	var tick <-chan time.Time

	if *verbose {
		tick = time.Tick(500 * time.Millisecond)
	}

	var nfiles, nbytes int64

loop:
	for {
		select {
		case size, ok := <-fileSizes:
			if !ok {
				break loop
			}
			nfiles++
			nbytes += size
		case <-tick:
			printDiskUsage(nfiles, nbytes)
		}
	}
	printDiskUsage(nfiles, nbytes)
}
xxx@MacBook-Pro-10 ~ % ./du1 -v $HOME /Users/qinjianquan/go/src/awesomeProject/src
du1:open /Users/qinjianquan/.Trash: operation not permitted
5525 files 1.0 GB
11329 files 1.0 GB
22952 files 1.0 GB
33588 files 1.0 GB
40356 files 1.0 GB
44873 files 2.0 GB
49163 files 2.0 GB

运行后我们发现还是有点慢,我们现在把walkDir函数放入goroutine,并且我们会对goroutine计数,当运行的goroutine数量为0时,我们结束这个环节,我们重新修改一下涉及函数中的局部代码

fileSizes := make(chan int64)

	var n sync.WaitGroup

	for _, root := range roots {
		n.Add(1)
		go walkDir(root, &n, fileSizes)
	}

	go func() {
		n.Wait()
		close(fileSizes)
	}()
	
	//
func walkDir(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())
			walkDir(subdir, n, fileSizes)
		} else {
			fileSizes <- entry.Size()
		}
	}
}

同理,上面的这个goroutine我们并没有做数量限制,为了防止它同时打开太多文件,我们来做个限制

var sema = make(chan struct{}, 20)

func dirents(dir string) []os.FileInfo {

	sema <- struct{}{}
	defer func() { <-sema }()

	entries, err := ioutil.ReadDir(dir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "du1:%v\n", err)
		return nil
	}
	return entries
}

现在这个程序要比之前快好几倍,当然这也和你的运行环境和机器配置有关。不过我们看到这种效率的提升是语言本身的优势

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值