0

In The Go Programming Language, Alan Donovan, page 264 he uses a mutex to read an int.

I don't understand why since an int will fit in a single word, so it cannot be a torn read.

I'm probably wrong, but how? Thanks.

--- Update with code ---

func Balance() int {
  mu.Lock()
  defer mu.Unlock()
  return balance
}

Then down the page

func Withdraw(amount int) bool {
  Deposit(-amount)
  if Balance() < 0 {
    Deposit(amount)
    return false // insufficient funds
  }
}

This design lets the balance become invalid, which can be observed by a reader, which is then fixed in the book with locking in the Withdraw() func.

Apologies really, I think I wasted everyone's time :( I set everyone up to fail answering this correctly.

My theory is that if the balance variable was checked (under lock) that there is sufficient funds before it mutates it, then the locking in the Balance() func would not be needed, but I still may be wrong, especially as people have mentioned reordering which is kind of mysterious.

Luke Puplett
  • 42,091
  • 47
  • 181
  • 266
  • 1
    I'm not a professional Go programmer, but I think that you can't simply read a value from integer variable, because in the conditions when your program executed on multiple CPUs your simple read can hit different CPU caches and this can cause different values for the same variable. Maybe mutex is an overkill for this simple task and instead of it you can use atomics: https://golang.org/pkg/sync/atomic/#LoadInt32 – Nikita Sivukhin Mar 07 '20 at 19:27
  • Agreed with the previous comment. Don't know what example that book is using, but a lock is useful to avoid race conditions and weird behavior when accessing the same memory from multiple goroutines. – Pheric Mar 07 '20 at 19:28
  • 2
    ["Benign Data Races: What Could Possibly Go Wrong?"](https://software.intel.com/en-us/blogs/2013/01/06/benign-data-races-what-could-possibly-go-wrong) explains why in detail – JimB Mar 07 '20 at 23:42
  • @Nikita Good answers, thank you. A few pages on and the book says "You may wonder why Balance() needs a mutex" and reasons about CPU caches, which is specifically what Nikita wrote. – Luke Puplett Mar 08 '20 at 12:46

3 Answers3

4

If you have a single goroutine, you need no special means to read and write variables.

If you have multiple goroutines, access to variables that are accessed from multiple goroutines and at least one of the accesses is a write must be synchronized.

Quoting from The Go Memory Model:

Within a single goroutine, reads and writes must behave as if they executed in the order specified by the program. That is, compilers and processors may reorder the reads and writes executed within a single goroutine only when the reordering does not change the behavior within that goroutine as defined by the language specification. Because of this reordering, the execution order observed by one goroutine may differ from the order perceived by another. For example, if one goroutine executes a = 1; b = 2;, another might observe the updated value of b before the updated value of a.

Although Go encourages another approach:

Do not communicate by sharing memory; instead, share memory by communicating.

So instead of using locks to protect variables, you should use channels to send new values and computation results to where it needs to be used, so reading shared variables will not be necessary. Channels are safe for concurrent use, data races cannot occur by design.

icza
  • 389,944
  • 63
  • 907
  • 827
3

Mutex is there to provide the necessary memory barriers and establish a happened-before relationship:

https://golang.org/ref/mem

The guarantee here is that once the mutex is unlocked, all the other goroutines that access the integer using the same mutex will see the value the integer has after the unlock. Without a mutex, there is no such guarantee; a goroutine may see the value of the int before a writer goroutine wrote to it.

Even though Go memory model does not explicitly say so, atomics guarantee the same as well, provided all goroutines read/write using atomics.

Burak Serdar
  • 46,455
  • 3
  • 40
  • 59
0

On page 267 it addresses my question, saying "You may wonder why the Balance() method needs mutal exclusion."

It puts it down to CPU caching as @Nikita specifically mentioned in his comment. The author also says there's no such thing as a benign data race which is what @JimB alluded to with his link in another comment.

Luke Puplett
  • 42,091
  • 47
  • 181
  • 266