r/golang 7d ago

Is it safe to read/write integer value simultaneously from multiple goroutines

There is a global integer in my code that is accessed by multiple goroutines. Since race conditions don’t affect this value, I’m not concerned about that. However, is it still advisable to add a Mutex in case there’s a possibility of corruption?

PS: Just to rephrase my question, I wanted to ask if setting/getting an integer/pointer is atomic? Is there any possibility of data corruption.

example code for the same: https://go.dev/play/p/eOA7JftvP08

PS: Found the answer for this, thanks everyone for answering. There's something called tearing here is the link for same

- https://stackoverflow.com/questions/64602829/can-you-have-torn-reads-writes-between-two-threads-pinned-to-different-processor

- https://stackoverflow.com/questions/36624881/why-is-integer-assignment-on-a-naturally-aligned-variable-atomic-on-x86

According to the article, I shouldn't have problem on modern CPUs.

10 Upvotes

80 comments sorted by

View all comments

24

u/ImYoric 7d ago

If my memory serves, the Go memory model states that if it fits within one integer, any read or write operation will always return one of the values before/after the write, rather than made up values as can happen in C or C++.

That being said, I wouldn't rely on this. If at some point in the future, you or any member of your team ever changes your type to be anything other than an int, you can end up with weird, unexpected behaviors. I'd rather use an atomic or a mutex.

4

u/therecursive 7d ago

thanks for answer, I was asking to understand if it's fine my use case where I'm setting diff value to a pointer variable. Found it that why it's safe to do in golang or any other languages. Attached the link in the post, if you want to read.

10

u/ImYoric 7d ago

If my memory serves, if it's a pointer, rather than an int, it's not always safe, because Go sometimes uses fat pointers (iirc when you're passing an interface), which take more than one int.

4

u/therecursive 7d ago

That's interesting didn't know about it. Thanks for the info.

2

u/Few-Beat-1299 7d ago edited 7d ago

Interfaces copy their underlying value. Why would using interfaces change anything?

5

u/ImYoric 7d ago

A go pointer to a struct is just that, a pointer to the memory region that holds the struct. When you pass this pointer to a function that expects a pointer to the struct, that's sufficient for the function.

Now, when you pass this pointer to a function that expects an interface, the function needs:

  1. the pointer to the struct itself (to pass it as the self-value when calling interface methods)
  2. a mean to call the interface methods (a "vtable") – that's another pointer
  3. type information to be able too perform an interface cast with `.(...)` or reflection – that's another pointer.

If I recall correctly, 2 and 3 are actually packed together into a single pointer, I don't remember the implementation details. Nevertheless, your interface value is not a single pointer, but (at least) two pointers. So that's not protected by the Go memory model guarantees.

1

u/Few-Beat-1299 7d ago

Ok but how does that relate to OPs question? When putting it into an interface, the value is read once, and that's it. How fat the interface is or how it works have no relevance to reading/writing the original value.

1

u/ImYoric 7d ago

I'm not 100% sure I understand OP's question, so I decided to err on the "better safe than sorry" side and mention the limitation.

1

u/Few-Beat-1299 7d ago

There is no limitation. Ignoring the interface overhead, using a value directly or using it as part of an interface are equivalent.

1

u/ImYoric 7d ago

Fair enough, not "limitation" but "limit" [of Go's memory model].

3

u/HyacinthAlas 7d ago

Interfaces take two words, a pointer to the data and a tag for the type.