Golang synchronization: Difference between revisions

From wikinotes
(Created page with "Synchronization tools are used to synchronize multiple concurrent codepaths so that your program can continue synchronously. = WaitGroups = <blockquote> WaitGroups are global...")
 
 
(7 intermediate revisions by the same user not shown)
Line 1: Line 1:
Synchronization tools are used to synchronize multiple concurrent codepaths so that your program can continue synchronously.
Synchronization tools are used to synchronize multiple concurrent codepaths so that your program can continue synchronously.
= Documentation =
<blockquote>
{| class="wikitable"
|-
| <code>sync</code> docs || https://pkg.go.dev/sync
|-
|}
</blockquote><!-- Documentation -->
= Atomics =
<blockquote>
<syntaxhighlight lang="go">
import "sync/atomic"
bool := atomic.Bool{}
prevVal : = bool.Swap(true)  // assigns true, returns prev-value
bool.Store(false)            // assigns true
</syntaxhighlight>
</blockquote><!-- Atomics -->
= Mutexes =
<blockquote>
== Mutex ==
<blockquote>
A lock used to synchronize threads. No wait-time is specified, you are responsible for re-trying the lock if desired.
<syntaxhighlight lang="go">
var lock = Mutex{}
lock.Lock()
lock.Unlock()
lock.TryLock()  // discouraged
</syntaxhighlight>
</blockquote><!-- Mutex -->
== RWMutex ==
<blockquote>
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
<syntaxhighlight lang="go">
import "sync"
var lock = RWMutex{}
// lock/unlock for reading
lock.RLock()
lock.RUnlock()
// lock/unlock for writing
lock.Lock()
lock.Unlock()
</syntaxhighlight>
It would be sensible to unlcok in <code>defer</code>red functions.
</blockquote><!-- RWMutex -->
</blockquote><!-- Mutexes -->


= 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 31: Line 94:
}
}
</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.
=== Remaining Waits ===
<blockquote>
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><!-- 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  # memory addr of object
p &*wg  # memory addr of obj referenced by pointer
</syntaxhighlight>
</blockquote><!-- Memory Address -->
</blockquote><!-- Debugging -->
</blockquote><!-- WaitGroups -->
</blockquote><!-- WaitGroups -->

Latest revision as of 04:32, 17 February 2023

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

Documentation

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

Atomics

import "sync/atomic"

bool := atomic.Bool{}
prevVal : = bool.Swap(true)  // assigns true, returns prev-value
bool.Store(false)            // assigns true

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   # memory addr of object
p &*wg  # memory addr of obj referenced by pointer