第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 int64for 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.Timeif *verbose {tick = time.Tick(500 * time.Millisecond)}var nfiles, nbytes int64loop:for {select {case size, ok := <-fileSizes:if !ok {break loop}nfiles++nbytes += sizecase <-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.WaitGroupfor _, 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
}

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

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

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

    8.8 示例: 并发的目录遍历 知识点 1.利用并发遍历并计算文件大小 2.利用select,优化打印文件大小 3.利用channel设置最大信号量,来防止打开文件过多 代码 func test_co ...

  2. 《Go语言圣经》第一章 - 读书笔记

    <Go语言圣经>第一章 - 读书笔记 第一章 Go语言入门 01 Hello World 02 命令行参数 练习 练习1.1 练习1.2: 练习1.3: 03 查找重复的行 例子运行 du ...

  3. 《Go语言圣经》第一章:入门-习题解答及读书笔记精华摘要

    1.1 Hello World gopl_hello.go package mainimport ("fmt" )func main() {fmt.Println("He ...

  4. Go语言圣经 - 第3章 基础数据类型

    第3章 基础数据类型 Go语言将数据类型分为了四类:基础类型.符合类型.引用类型和接口类型.基础类型:数字.字符串和布尔型:复合数据类型:数组和结构体:引用类型:指针.切片.字典.函数.通道,虽然数据 ...

  5. Go语言圣经 - 第11章 测试 - 11.1 go test 11.2 测试函数

    第11章 测试 软件测试是一个巨大的领域,但是Go语言的测试技术是相对比较低级的,它依赖一个Go test测试命令和一组按照约定方式编写的测试函数,测试命令可以运行这些函数 在实践中,编写测试代码和编 ...

  6. Go语言圣经 - 第10章 包和工具 - 10.7 工具

    第10章 包和工具 现在随便一个小程序可能就包含10000个函数,但是我们不可能一个个去构建,大部分还是来自于他人,这些函数通过类似包和模块的方式被重用 go语言的包超过100个,可以在终端中使用go ...

  7. Go语言圣经 - 第11章 测试 - 11.4 - 11.6

    第11章 测试 软件测试是一个巨大的领域,但是Go语言的测试技术是相对比较低级的,它依赖一个Go test测试命令和一组按照约定方式编写的测试函数,测试命令可以运行这些函数 在实践中,编写测试代码和编 ...

  8. Go语言圣经 - 第1章 入门 - 1.3 1.4 查找重复的行 GIF

    第1章 入门 1.3 查找重复的行 在1.2节,我们简单的了解了一些Go语言的基本语法,接下来我们再来看一个例子进一步学习 对文件做拷贝.打印.搜索和排序.统计或类似事情的程序都有一个差不多的程序结构 ...

  9. Go语言圣经 - 第7章 接口 - 7.9 表达式求值

    第7章 接口 接口类型是对其它类型行为的抽象和概括.接口类型不会和特定的实现细节绑定在一起,这种抽象的方式能让我们的函数更加的灵活和更具有适应能力 Go语言的接口比较特殊,因为它是满足隐式实现的.也就 ...

最新文章

  1. npm install 报权限错误,permission denied
  2. 2021世界人工智能大会最高奖项——卓越人工智能引领者奖(Super AI Leader,简称SAIL奖)在大会开幕式揭晓...
  3. 负载过高之外网抓数据
  4. Silverlight学习之——事件编程
  5. YII2源码阅读:autoload_real.php 22~23行
  6. Oracle osw监控工具的使用示例
  7. LeetCode-剑指 Offer 11. 旋转数组的最小数字
  8. Linux下挂载ISO文件
  9. 并行开发 —— 第六篇 异步编程模型
  10. CString Management (关于CString的所有操作)
  11. 【图论】【启发式搜索】【二分查找】[POJ 3897]Maze Stretching
  12. 您未被授权查看该页 您不具备使用所提供的凭据查看该目录或页的权限 HTTP 错误 401.1 - 未经授权:访问由于凭据无效被拒绝。...
  13. IDEA使用SVN上传项目
  14. 中望3d快捷键命令大全_中望CAD快捷键全集
  15. ThinkPad 笔记本BIOS设置手册
  16. Pytorch练习--绘制Loss曲线
  17. C语言中返回的0和1
  18. 实数单竖线,向量双竖线,范数双竖线加下标,矩阵单竖线
  19. 淘宝淘口令解密,解析,转换接口,API对接
  20. 计算机办公软件office的考试试题,中职计算机办公软件Word2010年最新考试试题.doc...

热门文章

  1. python编程调用设备串口发送数据
  2. 金山卫士开源---kclear 卫士垃圾清理
  3. 交叉熵损失函数优缺点_交叉熵损失函数
  4. 计算机科学与技术张萌,张萌-长安大学理学院
  5. matlab计算abc三相短路电流_什么是短路计算电压?
  6. 中学信息技术教材c语言,中学信息技术校本教材.doc
  7. 利用笔记本的无线网卡制做WIFI热点
  8. JavaFx还是Flex
  9. 奇客(Geek)的常用软件清单(操作系统支持OS X, Windows, Linux)
  10. 开放接口签名(Signature)实现