背景
在一个无聊的下午,我突发奇想统计任意目录下的文件个数。我也是进入程序界快两年的人勒,于是当我想从单线程进军多线程领域。未曾想到,死锁成为了我不可磨灭的噩梦。
基本功能需求
- 不漏数;
- 限制并发量,避免cpu频繁发生上下文切换
一个工人辛勤劳作版
func countFile(path string) {
if infos, err := ioutil.ReadDir(path); err != nil {
fmt.Println("some errors curd")
return
} else {
for _, file := range infos {
if file.IsDir() {
findFile(path + "/" + file.Name() + "/")
continue
}
countFiles++
}
}
}
只要目录的深度没有超过最大程序栈的容量,这个工人是可以正常工作,当然这里就不考虑那么深目录了。我们写个主函数编译运行一下:
var var s *string
var countFiles int32 = 0
func init() {
s = flag.String("f", ".", "use for path") // 默认当前执行目录
}
func main() {
flag.Parse()
start := time.Now()
countFile(*s)
fmt.Println("findFiles", time.Now().Sub(start).Milliseconds())
fmt.Println(countFiles,0)
}
go build ../test
./test -f /usr
运行结果:
一个工人要数24万个文件,心里肯定不平衡,为了公平起见我们让其他工人也参加这个数文件工作。
进入正片,雨露均沾版
思路:
每当遇到一个目录我们就让一个工人去数目录下的文件,当然工人不能无限分配,所以我们需要限制工人的数量。其次我们需要一个监工来负责安排工人,和统计结果,掌握整个进度。
package main
import (
"flag"
"fmt"
"io/ioutil"
"strings"
"sync/atomic"
"time"
)
var s *string
var (
countFiles = 0
res = make(chan bool)
path = make(chan string)
workDone = make(chan bool)
works = make(chan struct{},10)
)
func init() {
s = flag.String("f", ".", "use for path")
}
func main() {
flag.Parse()
start := time.Now()
works <- struct{}{}
go multiFindFile(*s, true)
deal()
first := time.Now()
fmt.Println("multiFind", first.Sub(start).Milliseconds())
}
// 监工
func deal() {
for true {
select {
case <-res:
countFiles++
case path_ := <-path:
works <- struct{}{}
go multiFindFile(path_, true)
case <-workDone:
<- works
if len(works) == 0 {
return
}
}
}
}
// 工人
func multiFindFile(path_ string) {
if infos, err := ioutil.ReadDir(path_); err == nil {
for _, file := range infos {
if file.IsDir() {
path <- path_ + "/" + file.Name() + "/"
continue
}
res <- true
}
workDone <- true
}
}
运行发现出现可怕的deadlock字样。那这个思路哪里会发生死锁问题呢?
根据描述的思路来看,我们采用了监工来安排工人,工人只愿意负责自己目录的文件而不愿意负责目录,而工人总数肯定是小于目录数,就会出现工人在等监工安排工人执行子目录,而监工在等工人完成当前目录。就会出现你等我,我等你的尴尬情况。专业称之为死锁。因而需要避免这一情况,则需要增加工人,或则给工人增加工作量。这里就不运行展示了,运行必然出现deadlock。下面看看“公正的”解决方案
解决方案
// 工人做的工作,本方案只能强行让工人做(加钱做不做?)。我(打工人):做做做(^.^)
func multiFindFile(path_ string, isMaster bool) {
/*
isMaster: true代表是创建了新的工人来做子目录工作;false代表工人用完了,只能工人自己负责旗下的所有目录文件
*/
if infos, err := ioutil.ReadDir(path_); err == nil {
for _, file := range infos {
if file.IsDir() {
if atomic.AddInt32(&works, -1) >= 0 {
path <- path_ + "/" + file.Name() + "/"
continue
}
multiFindFile(path_+"/"+file.Name()+"/", false)
continue
}
res <- true
}
if isMaster {
workDone <- true
}
}
}
// 监工做的工作
func deal() {
for true {
select {
case <-res:
countFiles++
case path_ := <-path:
go multiFindFile(path_, true)
case <-workDone:
if atomic.AddInt32(&works, 1) == 64 {
return
}
}
}
}
这样就可以正常运转,这个就交给读者朋友去测试了,其中还存在一些bug(例如还是会诞生一些不存在的工人来完成了工作,监工都觉得很诡异,无所谓反正我没花钱),读者可以自己修复一下,别让监工赚了黑心钱。
|
|
|
|
今天就到这里啦,该休息一下了(偷偷告诉你们,我测试过很多遍才想明白,没办法我太菜)。
思路参考:https://www.bilibili.com/video/BV1qT4y1c77u?spm_id_from=333.1007.top_right_bar_window_history.content.click
详见代码。