Golang synchronization: Difference between revisions

From wikinotes
Line 91: Line 91:
and even have a rough idea how many conditions it is still waiting for.
and even have a rough idea how many conditions it is still waiting for.


=== Remaining Waits ===
<blockquote>
How to debug, recap
How to debug, recap
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
Line 100: Line 102:
Comparing values, you can see <code>state1</code> is multiples or the original value.
Comparing values, you can see <code>state1</code> is multiples or the original value.
<syntaxhighlight lang="go">
<syntaxhighlight lang="go">
// new/satisfied waitgroup
// new/satisfied waitgroup           (4294967296 * 0 == 0)
// 4294967296 * 0 == 0
sync.WaitGroup {
sync.WaitGroup {
     noCopy: sync.noCopy {},
     noCopy: sync.noCopy {},
Line 108: Line 109:
}
}


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


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


// wait on 3x 'wg.Done()'
// wait on 3x 'wg.Done()'             (4294967296 * 3 == 12884901888)
// 4294967296 * 3 == 12884901888
sync.WaitGroup {
sync.WaitGroup {
         noCopy: sync.noCopy {},
         noCopy: sync.noCopy {},
Line 132: Line 130:
You can poke in the sourcecode for the details,
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.
but even without it you can use this to get a rough idea of what state your waitgroup is in and when.
</blockquote><!-- Remaining Waits -->
=== Memory Address ===
<blockquote>
Within [[golang delve]], you can find out if your object is pointing to the same memory address.
<syntaxhighlight lang="bash">
p &wg  // show memory address (even if 'wg' is a pointer)
</syntaxhighlight>
</blockquote><!-- Memory Address -->
</blockquote><!-- Debugging -->
</blockquote><!-- Debugging -->
</blockquote><!-- WaitGroups -->
</blockquote><!-- WaitGroups -->

Revision as of 02:57, 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.

Remaining Waits

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.

Memory Address

Within golang delve, you can find out if your object is pointing to the same memory address.

p &wg  // show memory address (even if 'wg' is a pointer)