Home > Software design >  Parallel algorithm in golang to sum of elements in a vector
Parallel algorithm in golang to sum of elements in a vector

Time:06-12

I am implementing some parallel algorithms as an exercise in Golang. Right now I am trying to sum all the elements in a vector, but to do that, I need a barrier. I googled around but I could not find anything that could help me.

This is what my code looks like:

package main

import (
    "fmt"
    "math"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    sumWorkerFunc := func(k int, a []int, br *sync.WaitGroup) {
        bound := int(math.Ceil(math.Log2(float64(k))))
        for i := 1; i < bound; i   {
            if k%int(math.Pow(2, float64(i))) == 0 {
                a[k] = a[k-int(math.Pow(2, float64(i-1)))]   a[k]
            }

            /* barrier here */
        }

        wg.Done()
    }

    a := []int{0, 1, 2, 3, 4, 5, 6, 7}

    fmt.Println("Before:")
    fmt.Println(a)

    workers := 8
    wg.Add(workers)

    for k := 0; k < workers; k   {
        go sumWorkerFunc(k, a, br)
    }
    wg.Wait()

    fmt.Println("After:")
    fmt.Println(a)
}

I need to wait for all the workers to be done before starting the next iteration as they need the results for the next iteration. This is what I tried to do:

package main

import (
    "fmt"
    "math"
    "sync"
)

func main() {
    var wg sync.WaitGroup

    sumWorkerFunc := func(k int, a []int, br *sync.WaitGroup) {
        bound := int(math.Ceil(math.Log2(float64(k))))
        for i := 1; i < bound; i   {
            if k%int(math.Pow(2, float64(i))) == 0 {
                a[k] = a[k-int(math.Pow(2, float64(i-1)))]   a[k]
            }

            br.Done()
            br.Wait() // this should not be here
            br.Add(1)
        }

        wg.Done()
    }

    a := []int{0, 1, 2, 3, 4, 5, 6, 7}

    fmt.Println("Before:")
    fmt.Println(a)

    workers := 8
    wg.Add(workers)

    var barrier sync.WaitGroup
    barrier.Add(workers)

    for k := 0; k < workers; k   {
        go sumWorkerFunc(k, a, &barrier)
    }
    wg.Wait()

    fmt.Println("After:")
    fmt.Println(a)
}

But I cannot place a Wait() there because it will be called by all the workers. What would be a correct way of implementing a barrier there? I am starting to think that maybe this problem is oriented more towards the shared memory model which may not suitable for Golang.

Thanks!

EDIT:

I added an example of what I am trying to achieve:

5      2      1      3      5       8      1      1
|      |      |      |      |       |      |      |
|_ _ _ 7      |_ _ _ 4      |_ _ _ 13      |_ _ _ 2
       |             |              |             |
       |_ _ _ _ _ _ 11              |_ _ _ _ _ _ 15
                     |                            |
                     |_ _ _ _ _ _ _ _ _ _ _ _ _  26

where each worker is responsible for one element of the array.

CodePudding user response:

You need more than just the one WaitGroup to coordinate this routine. Look at this pattern:

func main() {
    const size = 100
    var (
        wg sync.WaitGroup
        a  [size]int
    )

    // Fill array a with all ones
    for i := 0; i < size; i   {
        go func(x int) {
            wg.Add(1)
            a[x] = 1
            wg.Done()
        }(i)
    }

    wg.Wait()

    fmt.Println(a)
}

Each worker adds itself to the WaitGroup before doing work, then removes itself when it's done. Do you see the problem? Try running it yourself a few times and see the output.

wg.Wait() is only valid if you call it after all of the expected wg.Add(x) have been called. Since the wg.Add(1) are inside the goroutine without any other synchronization, we don't know for sure how many workers were added by the time main goes to wg.Wait(). For example, it's possible that out of the 100 workers, 50 of them all called wg.Add(1) and then wg.Done() before the remaining 50 did anything. So, wg.Wait() continues while 50 of the workers still have not completed. All of this is to say that wg.Add(x) must be synchronized!

var barrier sync.WaitGroup
barrier.Add(workers)

for k := 0; k < workers; k   {
    go sumWorkerFunc(k, a, &barrier)
}

In this case, the Add is synchronized because it happens before the workers begin to execute.

br.Done()
br.Wait()
br.Add(1)

There's no problem necessarily with having all of your workers call Wait, the problem is that the Add is not synchronized. You cannot use the WaitGroup to synchronize itself here. You need some additional feature (another WaitGroup, a lock, a channel, etc.) to create synchronization across the workers between rounds.

Here's the easiest solution I could come up with, which makes one WaitGroup per each round:

func main() {
    const (
        workers = 3
        rounds  = 5
    )

    work := func(i int, roundWgs []sync.WaitGroup) {
        for r := 0; r < rounds; r   {
            // This is the "work" we do each round
            fmt.Printf("round: %v, worker %v\n", r, i)

            // We are finished the current round, and will wait for the group.
            roundWgs[r].Done()
            roundWgs[r].Wait()
        }
    }

    // Each round of work has it's own WaitGroup, in which each worker must finish.
    var roundWgs = make([]sync.WaitGroup, rounds)
    for i := 0; i < rounds; i   {
        roundWgs[i].Add(workers)
    }

    // wg is our outermost WaitGroup, which waits until all work is done.
    var wg sync.WaitGroup
    wg.Add(workers)
    for i := 0; i < workers; i   {
        go func(j int) {
            defer wg.Done()
            work(j, roundWgs)
        }(i)
    }
    wg.Wait()
}

Output:

round: 0, worker 2
round: 0, worker 0
round: 0, worker 1
round: 1, worker 1
round: 1, worker 0
round: 1, worker 2
round: 2, worker 2
round: 2, worker 1
round: 2, worker 0
round: 3, worker 0
round: 3, worker 2
round: 3, worker 1
round: 4, worker 1
round: 4, worker 2
round: 4, worker 0

Again, the Adds are synchronized because they all happen before any workers begin.

CodePudding user response:

I think what you are trying to do is more like a shared memory scenario. It would be better if instead of deferring the wait you can put it just after the loop that starts the goroutines. And you should use a mutex to ensure mutual exclusion.

And I don't think order matters for this problem.

  •  Tags:  
  • go
  • Related