Go Parallel 3

This is the third part of Go Parallel series (part 1, part 2) about parallel programming with the Go language. Today we will looks at sync and sync/atomic packages.

But first, the previous part contains a serious drawback -- I forget to post a nice picture of a gopher. Let's fix it:

golang gopher

OK, now we can get back to parallel programming.

Main synchronization primitives in Go are channels. But Go is a shared memory programming system, and it provides all traditional synchronization primitives as well. Package sync contains Mutex, RWMutex (reader-writer mutex), Cond (condition variable), Once (for one-time actions) and WaitGroup (allows to wait for a group of subtasks). Package sync/atomic contains Load, Store, Add, Swap, CompareAndSwap atomic operations on numeric types and pointers.

Let's consider some examples where these synchronization primitives can be useful (both simplify code and improve performance). Let's say we do parallel graph traversal and want to execute some code only once per node. sync.Once is what we need here:

type Node struct {
    once sync.Once
    ...
}

func visit(n *Node) {
    n.once.Do(func() {
        ...
    })
    ...
}

You can see full code here.

If we need to do concurrent accesses to a hashmap, then sync.RWMutex is the simplest solution:

    m := make(map[int]int)
    var mu sync.RWMutex

    // update
    mu.Lock()
    m[k] = v
    mu.Unlock()

    // read
    mu.RLock()
    v := m[k]
    mu.RUnlock()

Full code is here.

If we are using parallel branch-and-bound approach and the result fits into a single variable (e.g. the best packing of knapsack), then we can use atomic operations to read/update it from several goroutines:

// checkUpperBound returns false if a branch with the given upper bound
// on result can't yield a result better then the current best result.
func checkUpperBound(best *uint32, bound uint32) bool {
    return bound > atomic.LoadUint32(best)
}

// recordResult updates the current best result with result res.
func recordResult(best *uint32, res uint32) {
    for {
        current := atomic.LoadUint32(best)
        if res < current {
            return
        }
        if atomic.CompareAndSwapUint32(best, current, res) {
            return
        }
    }
}

Full code is here.

Sync and sync/atomic packages also allow easy direct porting of existing software from other languages to Go.


This is not particularly related to the sync package, but here are some interesting HPC/Scientific Computations libraries that flew by me recently: Go Infiniband libraries, a bioinformatics library for GoGraph package, Go OpenCL bindingAutomatic Loop Parallelization of Go Programs.

So what are you waiting for? Go parallel!

Per informazioni complete sulle ottimizzazioni del compilatore, consultare l'Avviso sull'ottimizzazione