Memory synchronization by channel operations and mutexes

298 views
Skip to first unread message

Leo Baltus

unread,
Jun 15, 2020, 12:31:09 PM6/15/20
to golan...@googlegroups.com
from gopl chapter 9.4 'Memory synchronisation’

Synchronization primitives like channel communications and mutex operations cause the processor to flush out and commit all its accumulated writes so that the effects of goroutine execution up to that point are guaranteed to be visible to goroutines running on other processors.

I would like to better understand how this works. What is it that makes this ‘flush out’ to happen? Is this a system call?


Leo



Robert Engels

unread,
Jun 15, 2020, 12:40:48 PM6/15/20
to Leo Baltus, golan...@googlegroups.com
Depends on the processor but usually it entails a “fence” instruction.

> On Jun 15, 2020, at 11:31 AM, Leo Baltus <leo.b...@npo.nl> wrote:
>
> from gopl chapter 9.4 'Memory synchronisation’
>
> Synchronization primitives like channel communications and mutex operations cause the processor to flush out and commit all its accumulated writes so that the effects of goroutine execution up to that point are guaranteed to be visible to goroutines running on other processors.
>
> I would like to better understand how this works. What is it that makes this ‘flush out’ to happen? Is this a system call?
>
> —
> Leo
>
>
>
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/F663D146-9F4C-48BB-918A-E58D554B9B18%40npo.nl.

burak serdar

unread,
Jun 15, 2020, 12:42:16 PM6/15/20
to Leo Baltus, golang-nuts
It is usually done with a memory barrier:

https://en.wikipedia.org/wiki/Memory_barrier

Jan Mercl

unread,
Jun 15, 2020, 12:43:58 PM6/15/20
to Leo Baltus, golang-nuts
For example, the memory model allows this code

package main

var i int

func main() {
for {
i++
}
}

to never update the value of the i variable and use a value cached in
a CPU register. That means that other goroutines may not see the value
of i to ever change if there's no synchronization. Any synchronization
will force the register value to be actually stored in the i variable.

Such caching is an implementation detail, so it may or may not happen
with different targets and/or different Go compilers.

leo.b...@npo-data.nl

unread,
Aug 17, 2020, 6:54:55 AM8/17/20
to golang-nuts
Thanks. So I take it this must be handled by the compiler when generating assembly.

I have peeked in the src/cmd/compile/ directory , assuming it is hidden there, but that's a rabbit hole I do want to go in just yet.

What I am really looking for is knowing which instructions lead to memory-synchronisation. I assume there are  'Synchronisation primitives' which have this property and therefore other primitives have not.

E.g. A co-worker pointed out that you can avoid sync.Mutex by using this construct:

        if !atomic.CompareAndSwapInt32(&s.myLock, 0, 1) {
                fmt.Println("locked")
                return
        }
        defer atomic.StoreInt32(&s.myLock, 0)
        processData()

Would this synchronise memory?

Jan Mercl

unread,
Aug 17, 2020, 7:05:17 AM8/17/20
to leo.b...@npo-data.nl, golang-nuts
On Mon, Aug 17, 2020 at 12:55 PM leo.b...@npo-data.nl
<leo.b...@npo-data.nl> wrote:

> Thanks. So I take it this must be handled by the compiler when generating assembly.
>
> I have peeked in the src/cmd/compile/ directory , assuming it is hidden there, but that's a rabbit hole I do want to go in just yet.
>
> What I am really looking for is knowing which instructions lead to memory-synchronisation.

The specific instruction(s), if any for the particular CPU, are only
the implementation detail that can change. The thing that really
matters is the semantics of synchronization as described in "The Go
Memory Model": https://golang.org/ref/mem

Jesper Louis Andersen

unread,
Aug 17, 2020, 7:06:08 AM8/17/20
to leo.b...@npo-data.nl, golang-nuts
On Mon, Aug 17, 2020 at 12:54 PM leo.b...@npo-data.nl <leo.b...@npo-data.nl> wrote:
Thanks. So I take it this must be handled by the compiler when generating assembly.


Yes. The compiler generally follows the Go Memory Model reference specification: https://golang.org/ref/mem


E.g. A co-worker pointed out that you can avoid sync.Mutex by using this construct:

        if !atomic.CompareAndSwapInt32(&s.myLock, 0, 1) {
                fmt.Println("locked")
                return
        }
        defer atomic.StoreInt32(&s.myLock, 0)
        processData()

Would this synchronise memory?


Going straight by the specification: no. You need to use a sync.Mutex.

In practice, it might work on certain architectures, due to artifacts of how that specific CPU is implemented. Don't rely on it. You are usually better off using the standard primitives until you really need the added speed of atomics, and then constrain those to a few places in the program. Also, there are a lot of tricks you can play with mutexes to avoid lock contention, so measure measure measure.

leo.b...@npo-data.nl

unread,
Aug 17, 2020, 10:39:07 AM8/17/20
to golang-nuts
Playing Devil's Advocate: https://golang.org/ref/mem says:

> To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages.

So it explicitly mentions sync/atomic but only mentions sync.Mutex and sync.RWMutex further in the text.

Brian Candler

unread,
Aug 17, 2020, 11:19:16 AM8/17/20
to golang-nuts
On Monday, 17 August 2020 11:54:55 UTC+1, leo.b...@npo-data.nl wrote:
E.g. A co-worker pointed out that you can avoid sync.Mutex by using this construct:

        if !atomic.CompareAndSwapInt32(&s.myLock, 0, 1) {
                fmt.Println("locked")
                return
        }
        defer atomic.StoreInt32(&s.myLock, 0)
        processData()

Would this synchronise memory?

sync.atomic does synchronise memory, yes.

Indeed, the implementation of Mutex.Lock starts similarly to what you've written:

But it then takes a lot more care to avoid starvation, see comments at

In other words: you're better off using sync.Mutex if what you want is a mutex, rather than rolling your own.

jake...@gmail.com

unread,
Aug 17, 2020, 12:05:29 PM8/17/20
to golang-nuts
On Monday, August 17, 2020 at 11:19:16 AM UTC-4 b.ca...@pobox.com wrote:
On Monday, 17 August 2020 11:54:55 UTC+1, leo.b...@npo-data.nl wrote:
E.g. A co-worker pointed out that you can avoid sync.Mutex by using this construct:

        if !atomic.CompareAndSwapInt32(&s.myLock, 0, 1) {
                fmt.Println("locked")
                return
        }
        defer atomic.StoreInt32(&s.myLock, 0)
        processData()

Would this synchronise memory?

sync.atomic does synchronise memory, yes.

My understanding is that atomic does not 'synchronize memory' in general.  If you use a Mutex, then everything that happened before is 'synchronized'. If you use atomic then only the atomic variable is 'synchronized'. That is kind of the point of using atomic.

If I am wrong, please point me at the part of the Go Memory Model that says otherwise. Personally, I have always felt that the go memory model, and specifically the guarantees of the atomic package are not as rigorously defined as I would like. But in this case, I'm pretty sure I am correct.

Brian Candler

unread,
Aug 17, 2020, 4:54:48 PM8/17/20
to golang-nuts
Sorry, I should have been clearer: sync.atomic *does* synchronize the memory that it references.  I didn't mean to imply that it synchronizes all memory.

Ian Lance Taylor

unread,
Aug 17, 2020, 5:16:26 PM8/17/20
to jake...@gmail.com, golang-nuts
On Mon, Aug 17, 2020 at 9:06 AM jake...@gmail.com <jake...@gmail.com> wrote:
>
> My understanding is that atomic does not 'synchronize memory' in general. If you use a Mutex, then everything that happened before is 'synchronized'. If you use atomic then only the atomic variable is 'synchronized'. That is kind of the point of using atomic.
>
> If I am wrong, please point me at the part of the Go Memory Model that says otherwise. Personally, I have always felt that the go memory model, and specifically the guarantees of the atomic package are not as rigorously defined as I would like. But in this case, I'm pretty sure I am correct.

The behavior of the sync/atomic package is not specified at all by the
Go memory model. See https://golang.org/issue/5045.

I'm not sure I agree that using sync/atomic means that only that
memory location is synchronized (assuming I understand what that
means, which I may not). There are other meaningful patterns for
sync/atomic.

Ian

robert engels

unread,
Aug 17, 2020, 8:00:46 PM8/17/20
to Ian Lance Taylor, jake...@gmail.com, golang-nuts
See https://github.com/golang/go/issues/5045 which has been open for 7+ years.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

robert engels

unread,
Aug 17, 2020, 8:02:15 PM8/17/20
to Ian Lance Taylor, jake...@gmail.com, golang-nuts
Sorry Ian, I see you included this - I was typing the message and replied without reading the refresh.

Reply all
Reply to author
Forward
0 new messages