并发文件系统扫描
Concurrent filesystem scanning
我想获取目录中文件的文件信息(文件名和字节大小)。但是有很多子目录(~1000)和文件(~40 000)。
其实我的解决方法是使用filepath.Walk()获取每个文件的文件信息。但这很长。
func visit(path string, f os.FileInfo, err error) error {
if f.Mode().IsRegular() {
fmt.Printf("Visited: %s File name: %s Size: %d bytes\n", path, f.Name(), f.Size())
}
return nil
}
func main() {
flag.Parse()
root := "C:/Users/HERNOUX-06523/go/src/boilerpipe" //flag.Arg(0)
filepath.Walk(root, visit)
}
是否可以使用 filepath.Walk() 进行 parallel/concurrent 处理?
您可以通过修改 visit()
函数来进行并发处理,使其不进入子文件夹,而是为每个子文件夹启动一个新的 goroutine。
为了做到这一点,如果条目是一个目录,return 来自您的 visit()
函数的特殊 filepath.SkipDir
错误。不要忘记检查 visit()
中的 path
是否是 goroutine 应该处理的子文件夹,因为它也传递给 visit()
,如果没有这个检查,你将无休止地启动 goroutines对于初始文件夹。
此外,您还需要某种 "counter" 有多少 goroutines 仍在后台工作,为此您可以使用 sync.WaitGroup
.
这是一个简单的实现:
var wg sync.WaitGroup
func walkDir(dir string) {
defer wg.Done()
visit := func(path string, f os.FileInfo, err error) error {
if f.IsDir() && path != dir {
wg.Add(1)
go walkDir(path)
return filepath.SkipDir
}
if f.Mode().IsRegular() {
fmt.Printf("Visited: %s File name: %s Size: %d bytes\n",
path, f.Name(), f.Size())
}
return nil
}
filepath.Walk(dir, visit)
}
func main() {
flag.Parse()
root := "folder/to/walk" //flag.Arg(0)
wg.Add(1)
walkDir(root)
wg.Wait()
}
一些注意事项:
根据子文件夹中的 "distribution" 文件,这可能无法充分利用您的 CPU / 存储空间,例如 99% 的文件都在一个子文件夹中,goroutine 将仍然占用大部分时间。
另请注意,fmt.Printf()
调用是序列化的,因此这也会减慢进程。我假设这只是一个例子,实际上你会在内存中进行某种处理/统计。不要忘记保护对从 visit()
函数访问的变量的并发访问。
不用担心子文件夹数量多。这很正常,Go 运行时甚至可以处理数十万个 goroutine。
另请注意,性能瓶颈很可能是您的存储/硬盘速度,因此您可能无法获得所需的性能。达到一定程度(您的硬盘限制)后,您将无法提高性能。
同时为每个子文件夹启动一个新的 goroutine 可能不是最佳选择,您可能通过限制遍历文件夹的 goroutine 数量来获得更好的性能。为此,检查并使用工作池:
我想获取目录中文件的文件信息(文件名和字节大小)。但是有很多子目录(~1000)和文件(~40 000)。
其实我的解决方法是使用filepath.Walk()获取每个文件的文件信息。但这很长。
func visit(path string, f os.FileInfo, err error) error {
if f.Mode().IsRegular() {
fmt.Printf("Visited: %s File name: %s Size: %d bytes\n", path, f.Name(), f.Size())
}
return nil
}
func main() {
flag.Parse()
root := "C:/Users/HERNOUX-06523/go/src/boilerpipe" //flag.Arg(0)
filepath.Walk(root, visit)
}
是否可以使用 filepath.Walk() 进行 parallel/concurrent 处理?
您可以通过修改 visit()
函数来进行并发处理,使其不进入子文件夹,而是为每个子文件夹启动一个新的 goroutine。
为了做到这一点,如果条目是一个目录,return 来自您的 visit()
函数的特殊 filepath.SkipDir
错误。不要忘记检查 visit()
中的 path
是否是 goroutine 应该处理的子文件夹,因为它也传递给 visit()
,如果没有这个检查,你将无休止地启动 goroutines对于初始文件夹。
此外,您还需要某种 "counter" 有多少 goroutines 仍在后台工作,为此您可以使用 sync.WaitGroup
.
这是一个简单的实现:
var wg sync.WaitGroup
func walkDir(dir string) {
defer wg.Done()
visit := func(path string, f os.FileInfo, err error) error {
if f.IsDir() && path != dir {
wg.Add(1)
go walkDir(path)
return filepath.SkipDir
}
if f.Mode().IsRegular() {
fmt.Printf("Visited: %s File name: %s Size: %d bytes\n",
path, f.Name(), f.Size())
}
return nil
}
filepath.Walk(dir, visit)
}
func main() {
flag.Parse()
root := "folder/to/walk" //flag.Arg(0)
wg.Add(1)
walkDir(root)
wg.Wait()
}
一些注意事项:
根据子文件夹中的 "distribution" 文件,这可能无法充分利用您的 CPU / 存储空间,例如 99% 的文件都在一个子文件夹中,goroutine 将仍然占用大部分时间。
另请注意,fmt.Printf()
调用是序列化的,因此这也会减慢进程。我假设这只是一个例子,实际上你会在内存中进行某种处理/统计。不要忘记保护对从 visit()
函数访问的变量的并发访问。
不用担心子文件夹数量多。这很正常,Go 运行时甚至可以处理数十万个 goroutine。
另请注意,性能瓶颈很可能是您的存储/硬盘速度,因此您可能无法获得所需的性能。达到一定程度(您的硬盘限制)后,您将无法提高性能。
同时为每个子文件夹启动一个新的 goroutine 可能不是最佳选择,您可能通过限制遍历文件夹的 goroutine 数量来获得更好的性能。为此,检查并使用工作池: