【问题标题】:How to create recursive concurrent tree search in go如何在 Go 中创建递归并发树搜索
【发布时间】:2020-11-17 16:27:27
【问题描述】:

有没有合适的方法在 Go 中的递归树搜索中实现并发?

以下代码显示了对具有 id 和 children 属性的节点树的基本递归树搜索。 如果我找到结果我可以关闭频道但如果没有结果如何处理?

// Node is a struct containing int field ID and []*Node field Children.
type Node struct {
    ID       int
    Children []*Node
}

// Find search for a record by id inside an array of records
func Find(node *Node, id int) *Node {

    if node.ID == id {
        return node
    }

    for _, c := range node.Children {
        found := Find(c, id)
        if found != nil {
            return found
        }
    }

    return nil
}

【问题讨论】:

  • 是的,你的实现会为你的树的每个分支启动一个 goroutine,因此如果它进入一个不包含值的分支,它将浏览它直到结束。此外,写一个封闭的通道是一个错误。另外,生成那么多 goroutine 效率不高。在您的伪代码中,您正在关闭通道以表示找到了一个值。如果值存在两次会发生什么?找到值是否一定意味着您已经结束浏览树?
  • 这不是一个小问题,也就是说,我相信这是您没有得到帮助的原因之一。拥有多个 goroutine 在某些情况下可以提高算法的速度,但在其他情况下可能会有害。生成的例程越多,对运行时处理上下文切换施加的压力就越大,go 肯定能够并行处理数以百万计的例程,但它是有代价的。您必须始终权衡成本和收益。在您的伪代码中生成一个例程来执行比较并不好。
  • 想想你的问题我的一些问题是,如何检测搜索结束,如何对树进行分区,如何防止在某些边缘情况下出现愚蠢的性能陷阱,总的来说,它真的值得吗?非并发编程执行起来非常快速高效。
  • Is there a possible way to do it?。是的。很多种方法。总是赢吗?这么简单的数据结构能做到吗?不太清楚。
  • 我强烈建议您搜索、查看和学习围棋作者和专业人士提供的许多在线会议,以便在继续之前了解更多信息。

标签: go recursion concurrency


【解决方案1】:

这是一个简单而愚蠢的基准测试,我希望它有助于 OP 更好地理解并发编程的简单实现的影响。

package main_test

import (
    "sync"
    "testing"
)

func SearchNonConcurrently(v int, mySlice []int) int {
    for idx, i := range mySlice {
        if i == v {
            return idx
        }
    }
    return -1
}

func SearchConcurrently(v int, mySlice []int) int {
    var wg sync.WaitGroup
    wg.Add(len(mySlice))
    res := make(chan int)
    for idx, i := range mySlice {
        go func(idx, i int) {
            defer wg.Done()
            if i == v {
                res <- idx
            }
        }(idx, i)
    }
    go func() {
        wg.Wait()
        close(res)
    }()
    idx, ok := <-res
    if !ok {
        return -1
    }
    return idx
}

func BenchmarkSearchNonConcurrently(b *testing.B) {
    mySlice := make([]int, 10000)
    mySlice[len(mySlice)-1] = 1
    for n := 0; n < b.N; n++ {
        SearchNonConcurrently(1, mySlice)
    }
}

func BenchmarkSearchConcurrently(b *testing.B) {
    mySlice := make([]int, 10000)
    mySlice[len(mySlice)-1] = 1
    for n := 0; n < b.N; n++ {
        SearchConcurrently(1, mySlice)
    }
}

结果:

$ go test -bench=. -benchmem -count=4
goos: linux
goarch: amd64
pkg: test/bencslice
BenchmarkSearchNonConcurrently-4       85780         13831 ns/op           0 B/op          0 allocs/op
BenchmarkSearchNonConcurrently-4       83619         13880 ns/op           0 B/op          0 allocs/op
BenchmarkSearchNonConcurrently-4       87128         14050 ns/op           0 B/op          0 allocs/op
BenchmarkSearchNonConcurrently-4       87366         13837 ns/op           0 B/op          0 allocs/op
BenchmarkSearchConcurrently-4            235       4867257 ns/op        2200 B/op          6 allocs/op
BenchmarkSearchConcurrently-4            244       4896937 ns/op        3360 B/op          8 allocs/op
BenchmarkSearchConcurrently-4            240       4871934 ns/op         453 B/op          2 allocs/op
BenchmarkSearchConcurrently-4            244       4884617 ns/op        3352 B/op          8 allocs/op
PASS
ok      test/bencslice  12.087s
$ go version
go version go1.15.2 linux/amd64

【讨论】:

    【解决方案2】:

    我创建了以下代码。我唯一的遗憾是,即使找到了解决方案,它仍然会继续创建 goroutine。我的意思是,如果我使用ID = 5 搜索节点,那么对于具有 1000 个元素的排序树,它会不断打开超过 900 个的案例。

    func FindConcurrent(node *Node, id int, c chan *Node) {
        select {
        case <-c:
            return
        default:
        }
    
        if node.ID == id {
            c <- node
            close(c)
            return
        }
    
        for _, child := range node.Children {
            go FindConcurrent(child, id, c)
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-05-03
      • 2013-10-24
      • 1970-01-01
      • 2016-09-07
      • 1970-01-01
      • 2017-09-26
      • 2014-01-02
      相关资源
      最近更新 更多