等价二叉树练习,实现“并发”
Posted
技术标签:
【中文标题】等价二叉树练习,实现“并发”【英文标题】:Equivalent Binary Trees Exercise, achieving the "concurrency" 【发布时间】:2021-02-04 16:03:35 【问题描述】:我正在解决 A Tour of Go 中的练习,Equivalent Binary Tree。
这个练习需要实现一个Walk
函数,该函数应该遍历一棵树并将所有值从树中有序地发送到通道。
练习声明指出:
...我们将使用 Go 的并发和通道来编写一个简单的解决方案。
阅读该行,我认为以一种为每个左/右子树启动一个 goroutine 并使 Walk
比非并发运行更快(关于时间复杂度)的方式来实现 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
阻塞?
【问题讨论】:
记住,go Somefunc()
正在一个新的 goroutine 中运行 Somefunc
。
一旦你的代码到达range
块,它就永远不会离开。 range
非常适合处理频道,但在您在频道上调用 close
之前,请将其视为无限循环。
@NateH06 它确实被关闭了,在第一行的defer
中。
这个问题是discussed on meta。如果任何亲密的选民愿意分享他们在问题中遗漏的内容或不清楚的地方,那将不胜感激。
【参考方案1】:
右子树中的值需要一个缓冲区(例如缓冲通道),因为它们必须持续存在,直到它们之前的值(<-lch
和 t.Value
)被消耗掉。
为大小未知的子树准备缓冲区有几个含义
如果使用缓冲通道make(chan int, N)
,则选择N
至关重要。如果N
恰好对于子树来说太小,则子树的 goroutine 会很快阻塞。如果它太大,内存就会被不必要地浪费。
使用自动调整缓冲区大小实现自定义通道可能是一种解决方案,但它会带来额外的复杂性和开销。我不知道惩罚是否抵消了性能提升。这取决于自定义通道如何管理其内部缓冲区或这些缓冲区池的管理方式。
在我看来,没有简单通用的解决方案来解决这种问题,“遍历一个随机的、未知的树并挤出并发性”。
【讨论】:
以上是关于等价二叉树练习,实现“并发”的主要内容,如果未能解决你的问题,请参考以下文章