go - 等价二叉树练习,实现“并发”
问题描述
我正在解决A Tour of Go中的练习,等效二叉树。这个练习需要实现一个Walk
函数,该函数应该遍历一棵树并将所有值从树有序地发送到通道。
演习声明指出:
... 我们将使用 Go 的并发和通道来编写一个简单的解决方案。
阅读该行,我认为以Walk
一种为每个左/右子树启动一个 goroutine 并使其Walk
比非并发版本运行得更快(关于时间复杂度)的方式来实现是具有挑战性的。让我用代码更详细地解释一下。
这是我早期的代码Walk
:
func Walk(t *tree.Tree, ch chan int) {
defer close(ch)
if t == nil { return }
lch, rch := make(chan int), make(chan int)
go Walk(t.Left, lch)
for v := range lch { ch <- v }
ch <- t.Value
go Walk(t.Right, rch)
for v := range rch { ch <- v }
return
}
它肯定使用 goroutines,但实际上与没有 goroutines 的遍历没有什么不同,因为 ealry 会for v := range lch { ... }
延迟go Walk(t.Right, rch)
直到结束。
向上移动没有任何区别go Walk(t.Right, rch)
:
func Walk(t *tree.Tree, ch chan int) {
defer close(ch)
if t == nil { return }
lch, rch := make(chan int), make(chan int)
go Walk(t.Left, lch)
go Walk(t.Right, rch)
for v := range lch { ch <- v }
ch <- t.Value
for v := range rch { ch <- v }
}
go Walk(t.Left, lch)
沿着整个左子树(以蓝色为根)行走,并立即接收lch
从中发送的值。
go Walk(t.Right, rch)
也尝试走,但被阻塞,因为ch <- t.Value
右子树的最左边节点(红色)这样做并传播阻塞。这种状态一直持续到for v := range rch { ch <- v }
达到。
我的问题:如何以Walk
goroutines(对应左或右)ch <- t.Value
尽可能避免被阻塞的方式实现?
解决方案
右子树中的值需要一个缓冲区(例如缓冲通道),因为它们必须持续存在,直到那些 (<-lch
和t.Value
) 之前的值被消耗掉。
为大小未知的子树准备缓冲区有几个含义
- 如果使用缓冲通道 ,
make(chan int, N)
则选择N
至关重要。如果N
恰好对于子树来说太小,则子树的 goroutine 会很快阻塞。如果它太大,内存就会被不必要地浪费。 - 使用自动调整缓冲区大小实现自定义通道可能是一种解决方案,但它会带来额外的复杂性和开销。我不知道惩罚是否会抵消性能提升。这取决于自定义通道如何管理其内部缓冲区或如何管理这些缓冲区。
在我看来,对于这种“遍历一个随机的、未知的树,并发性被挤出”的问题,没有简单而通用的解决方案。
推荐阅读
- javascript - VivusJS 动画调用正确,但视觉上没有任何反应(或发生得太快而无法发现)
- python - 创建具有最大和最小限制的运行总计
- python - 删除与 Pandas 中的列名具有相同值的行
- pandas - 如何从多个批次中获取分类metrics_report的摘要数据框
- flutter - 实现构建后出现一个appbar
- javascript - 如何将变量(文件名)从外部 php 文件传递给 javascript?
- python-3.x - 删除 HTML 原始数据中的所有 CSS FORMAT
- python - 为什么所有的 Nan 值都没有被替换?
- ionic-framework - 在 ionic 5 组件中找不到货币管道错误
- docker - opentilemaps 下载挂起