Golang四叉树的递归并发

时间:2018-07-02 17:13:09

标签: go recursion concurrency goroutine quadtree

我正在尝试使用go例程共享递归查找来并行化四叉树查找。

我的Quadtree结构如下:

type Quadtree struct {
    Rectangle //Boundary of the quadtree
    Points   []Point
    Ne       *Quadtree
    Se       *Quadtree
    Sw       *Quadtree
    Nw       *Quadtree
    Divided  bool
    Capacity int
    Parent   *Quadtree
}

我有一个名为QueryForNearestPoints的方法,该方法接受PointsearchRadius并返回四叉树中searcharea内的所有点。如果在给定的searchRadius中找不到点,则增加searchRadius并重试。

points是将搜索半径中的点发送到的通道。我还使用sync.WaitGroup wg等待所有go例程结束。

func (q *Quadtree) QueryForNearestPoints(p Point, searchRadius float64) []QueryResult {
    searcharea = Circle{p, searchRadius}
    var testResults []QueryResult

    for {

        go func() {
        loop:
            for {
                select {
                case item, _ := <-points:
                    testResults = append(testResults, item)
                case <-done:
                    break loop
                }
            }
        }()
        wg.Add(1)
        go q.query(searcharea)
        wg.Wait()
        done <- true
        if len(testResults) > 0 {
            break
        } else {
            // Proportionally increase the search radius if no points are found.
            searcharea = Circle{p, searcharea.Radius + searcharea.Radius*50/100}
        }
    }
    return testResults
}

并行化四叉树的查询方法:

func (q *Quadtree) query(area Circle) {
    defer wg.Done()
    if !q.overlapsCircle(area) {
        return
    }
    if len(q.Points) > 0 {
        for i, point := range q.Points {
            if area.contains(point) {
                points <- QueryResult{point, i, q}
            }
        }
    }
    if q.Divided {
        wg.Add(4)
        go q.Ne.parallelquery(area)
        go q.Se.parallelquery(area)
        go q.Sw.parallelquery(area)
        go q.Nw.parallelquery(area)
    }
}

(q *Quadtree) parallelquery方法:

func (q *Quadtree) parallelquery(area Circle) {
    semaphore <- struct{}{}
    defer func() {
        <-semaphore
    }()
    q.query(area)
}

如果我没记错的话,这是一个CPU约束的工作负载,因此拥有1000个go-routine不会有帮助,所以我使用缓冲通道semaphore作为计数信号量以确保在任何时候都只有n个go例程处于活动状态。(在这里我将n设置为4,因为我的计算机有4 cpus。)

唯一的问题是,这种方法比正常的非并行顺序方法要慢得多。我在这里想念什么?我怎样才能使它更快?

1 个答案:

答案 0 :(得分:2)

这要慢得多,因为您在每个goroutine中所做的工作非常便宜,并且goroutine太多。

对于那些会在此答案中批评术语的人,我将互换使用并行性和并发性。即使严格地说,并行性是运行时/计算机的属性,并发性是代码的属性。如果某些代码在具有单个处理器的硬件上运行,则仍然可以并发,但是不会并行运行。

此处的并发版本较慢的原因是,使用通道等时仍需要进行一些同步。在幕后,通道使用互斥体来同步通道上的发送/接收。这意味着您正在引入goroutine之间的竞争。

每次调用parallelquery(area)时,您都将启动另外4个必须调度的goroutine,但是(使用信号量通道)您将并发goroutine的数量限制为4。

在n次递归之后,您有4 ^ n个goroutine都试图从信号量通道接收值,这会引起很多争用。

对此使用阻塞CPU探查器,您可能会发现大部分执行时间都花在争用上,试图从信号量中获取令牌。