-
Notifications
You must be signed in to change notification settings - Fork 289
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(concurrency): add concurrency optimization
- Loading branch information
Showing
1 changed file
with
89 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ | |
* @Author: shgopher [email protected] | ||
* @Date: 2024-02-26 00:23:06 | ||
* @LastEditors: shgopher [email protected] | ||
* @LastEditTime: 2024-02-27 23:21:55 | ||
* @LastEditTime: 2024-03-02 01:39:11 | ||
* @FilePath: /GOFamily/并发/并发优化/README.md | ||
* @Description: | ||
* | ||
|
@@ -51,4 +51,92 @@ func main(){ | |
|
||
|
||
## 优先使用 channel + context 的方法去优雅关闭 | ||
|
||
## 使用方去决定是否并发 | ||
```go | ||
func ListDirectory(dir string) chan string | ||
``` | ||
这是一个返回目录下所有文件路径的函数,它返回的是一个 channel,很明显,这跟题目所说的让使用方去决定是否并发是相违背的 | ||
|
||
因为在这个函数体内部,已经完成了一个 goroutine 的创建,但是作为使用方,我们并不能说一定要使用并发,而且你也无法控制发送的停止,假设你在这个函数内部使用一个 close 作为关闭的信号,也是有问题的,比如我想恢复读取,那么你已经 close 了,该如何继续开启呢? | ||
|
||
还有一个问题,这个函数返回的是一个 channel,但是并没有返回 error,我们如果去判断 channel 的状态,并不能分辨是读取完毕 channel 被 close 还是出现 error,然后 channel 被关闭了,这就是二象性的问题,你不能设置二象性这种状态的函数,就跟刚才说的那样,你无法获悉真实的最精准的状态。 | ||
|
||
也就是说你的发送过程不够透明,调用方无法决定暂停,恢复读取这些行为 | ||
|
||
再提一个需求,我只需要读取的数据中的某些符合规定的路径,那么你如果作为一个黑箱的函数,完全无法做到定制化对不对 | ||
|
||
那么为什么不能设计成一个普通函数,然后在调用方再决定是否并发这个行为模式呢? | ||
|
||
我们看一下 go 语言标准库中的用法 | ||
```go | ||
filepath.Walk(root string,fn filepath.WalkFunc) error | ||
``` | ||
很明显这是一个实现了功能的普通函数,那么让我们分别看看它的普通模式和并发模式 | ||
```go | ||
// 普通模式 | ||
|
||
err := filepath.Walk(".", func(path string, info fs.FileInfo, err error) error { | ||
if err != nil { | ||
fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err) | ||
return err | ||
} | ||
if info.IsDir() && info.Name() == subDirToSkip { | ||
fmt.Printf("skipping a dir without errors: %+v \n", info.Name()) // 可以看到我们返回的err是不同的err | ||
return filepath.SkipDir | ||
} | ||
// 定制需求 | ||
if path != "xxx"{ | ||
fmt.Printf("visited file or dir: %q\n", path) | ||
} | ||
return nil | ||
}) | ||
|
||
if err != nil { | ||
fmt.Printf("error walking the path %q: %v\n", tmpDir, err) | ||
return | ||
} | ||
``` | ||
那么再让我们看一下并发模式 | ||
```go | ||
// 并发模式 | ||
go func() { | ||
defer close(value) | ||
err <- filepath.Walk(root, func(path string, info os.FileInfo, err error) error { | ||
if err != nil { | ||
return err | ||
} | ||
// if the file is noe regular, it mean the file is done,you should return | ||
if !info.Mode().IsRegular() { | ||
return nil | ||
} | ||
value <- path | ||
return nil | ||
}) | ||
}() | ||
``` | ||
解释一下这个 Walk 的函数本质,它传入一个路径和一个函数,这个函数拥有的参数是一个要输出的路径,一个文件信息和一个错误类型,返回一个错误类型,参数函数被执行的时候 walk 底层会将真实 path 值作为实际参数传入这个函数里,所以我们可以在 path 中,指定一个 channel 去作为流的方式导出数据。 | ||
|
||
```go | ||
// walk 的底层源码 | ||
for _, name := range names { | ||
filename := Join(path, name) | ||
fileInfo, err := lstat(filename) | ||
if err != nil { | ||
|
||
// 这里将读取到的path值传入了 walkFn 函数里,来完成实际参数的赋值行为 | ||
if err := walkFn(filename, fileInfo, err); err != nil && err != SkipDir { | ||
return err | ||
} | ||
} else { | ||
err = walk(filename, fileInfo, walkFn) | ||
if err != nil { | ||
if !fileInfo.IsDir() || err != SkipDir { | ||
return err | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
可以看到这个函数的实现完成了基本的功能,既可以并发,又可以不并发 |