Golang synchronization: Difference between revisions

From wikinotes
Line 52: Line 52:
= WaitGroups =
= WaitGroups =
<blockquote>
<blockquote>
WaitGroups are global threadsafe counters that are incremented/decremented.
== Basics ==
<blockquote>
WaitGroups are global threadsafe counters that are incremented/decremented.<br>
You can think of them as semaphores.


<syntaxhighlight lang="go">
<syntaxhighlight lang="go">
Line 80: Line 83:
}
}
</syntaxhighlight>
</syntaxhighlight>
</blockquote><!-- Basics -->
== Debugging ==
<blockquote>
Inspecting the waitgroup struct in a debugger can be very helpful.<br>
Notably, you can tell if a WaitGroup has satisfied all wait conditions,<br>
and even have a rough idea how many conditions it is still waiting for.
How to debug, recap
<syntaxhighlight lang="bash">
dlv test foo.com/x/project/./internal/pkg -- -test.run TestSomething  # start debugger
c                                                                    # start executing
p wg                                                                  # pring your WaitGroup value
</syntaxhighlight>
Comparing values, you can see <code>state1</code> is multiples or the original value.
<syntaxhighlight lang="go">
// new/satisfied waitgroup
// 4294967296 * 0 == 0
sync.WaitGroup {
    noCopy: sync.noCopy {},
    state1: 0,
    state2: 1,
}
// wait 1x 'wg.Done()'
// 4294967296 * 1 == 4294967296
sync.WaitGroup {
        noCopy: sync.noCopy {},
        state1: 4294967296,
        state2: 0,}
// wait on 2x 'wg.Done()'
// 4294967296 * 2 == 8589934592
sync.WaitGroup {
        noCopy: sync.noCopy {},
        state1: 8589934592,
        state2: 0,}
// wait on 3x 'wg.Done()'
// 4294967296 * 3 == 12884901888
sync.WaitGroup {
        noCopy: sync.noCopy {},
        state1: 12884901888,
        state2: 0,}
</syntaxhighlight>
You can poke in the sourcecode for the details,
but even without it you can use this to get a rough idea of what state your waitgroup is in and when.
</blockquote><!-- Debugging -->
</blockquote><!-- WaitGroups -->
</blockquote><!-- WaitGroups -->

Revision as of 02:50, 31 July 2022

Synchronization tools are used to synchronize multiple concurrent codepaths so that your program can continue synchronously.

Documentation

sync docs https://pkg.go.dev/sync

Mutexes

Mutex

A lock used to synchronize threads. No wait-time is specified, you are responsible for re-trying the lock if desired.

var lock = Mutex{}

lock.Lock()
lock.Unlock()
lock.TryLock()  // discouraged

RWMutex

Variation of a mutex where:

  • any number of items can acquire a lock for reading
  • only one item can acquire lock for writing
  • you cannot write while any other lock is reading
import "sync"

var lock = RWMutex{}

// lock/unlock for reading
lock.RLock()
lock.RUnlock()

// lock/unlock for writing
lock.Lock()
lock.Unlock()

It would be sensible to unlcok in deferred functions.

WaitGroups

Basics

WaitGroups are global threadsafe counters that are incremented/decremented.
You can think of them as semaphores.

wg = sync.WaitGroup{}

wg.Add(3)  // add 3x increments to countdown
wg.Done()  // decrement waitgroup by one
wg.Wait()  // wait for waitgroup to reach 0
// wait-groups are threadsafe proxy objects, intended to be globally accessible
var wg = sync.WaitGroup{}

func printHi() {
    fmt.Println("hi")
    wg.Done()
}

func main() {
    wg.Add(3)
    for i=0; i<3; i++ {
        go printHi()
    }
    wg.Wait()
    fmt.Println("bye")
}

Debugging

Inspecting the waitgroup struct in a debugger can be very helpful.
Notably, you can tell if a WaitGroup has satisfied all wait conditions,
and even have a rough idea how many conditions it is still waiting for.

How to debug, recap

dlv test foo.com/x/project/./internal/pkg -- -test.run TestSomething  # start debugger
c                                                                     # start executing
p wg                                                                  # pring your WaitGroup value

Comparing values, you can see state1 is multiples or the original value.

// new/satisfied waitgroup
// 4294967296 * 0 == 0
sync.WaitGroup {
    noCopy: sync.noCopy {},
    state1: 0,
    state2: 1,
}

// wait 1x 'wg.Done()'
// 4294967296 * 1 == 4294967296
sync.WaitGroup {
        noCopy: sync.noCopy {},
        state1: 4294967296,
        state2: 0,}

// wait on 2x 'wg.Done()'
// 4294967296 * 2 == 8589934592
sync.WaitGroup {
        noCopy: sync.noCopy {},
        state1: 8589934592,
        state2: 0,}

// wait on 3x 'wg.Done()'
// 4294967296 * 3 == 12884901888
sync.WaitGroup {
        noCopy: sync.noCopy {},
        state1: 12884901888,
        state2: 0,}

You can poke in the sourcecode for the details, but even without it you can use this to get a rough idea of what state your waitgroup is in and when.