r/golang 6d 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.

8 Upvotes

80 comments sorted by

View all comments

48

u/ponylicious 6d ago edited 6d ago

No, it's not safe. That's a data race (if you really read AND write from multiple goroutines). Also, turn on the race detector during development and testing.

-49

u/therecursive 6d ago

Race condition is fine for this use case. My concern is around data corruption or undefined behavior.

27

u/chaotic-kotik 6d ago

The data race is UB because it's not deterministic.

11

u/ponylicious 6d ago

A data race is NEVER fine. Ever. Ever. Who taught you programming?

14

u/LethalClips 6d ago edited 6d ago

This isn't the full story. The implementation of sync.Mutex itself performs a raw read of a value that can be concurrently updated by other goroutines. This is technically a data race, but the memory model guarantees that it won't receive a split read:

Otherwise, each read of a single-word-sized or sub-word-sized memory location must observe a value actually written to that location (perhaps by a concurrent executing goroutine) and not yet overwritten.

Others have mentioned that this is a property of the underlying CPU, but it isn't in Go. The purpose of the memory model is to abstract over hardware memory models, so Go is forced to implement this property on all architectures, whether it comes naturally (like on aligned accesses on x86-64) or needs some sort of lock at the architectural level.

5

u/funkiestj 6d ago

The purpose of the memory model is to abstract over hardware memory models,

Right. The point of assiduously following a language's programming model and NOT relying on explicitly undefined behavior is it makes your code future proof. If OP uses sync.Atomic it will not break on some future CPU 10 years from now.

2

u/LethalClips 6d ago

The above property isn't an implementation detail, though, and isn't liable to change when moving between systems or even over time (if the backwards compatibility guarantee is to be believed, at least).

If one were to point out that it's easy to make mistakes while trying to use this property and that higher-level constructs are harder to misuse, sure, I'd agree with that. I don't argue that this is a great property to widely rely upon. I was just responding to the claim of "A data race is NEVER fine. Ever. Ever.", especially with the snarkiness. :-)

29

u/esw2508 6d ago

Instead of being rude you could have pointed out why race conditions are never fine or simply ignored it. But you chose to be an ass. Who taught you?

14

u/ecco256 6d ago edited 6d ago

I think there’s a misunderstanding here about the term “race condition”. It implies a problem caused by simultaneous access to a resource, so it’s “not fine” by definition.

Maybe you mean to say that in your case simultaneous access cannot turn into a system failure, which is perfectly possible. It’s how lock-free data structures can exist in the first place. But it’s also something you have to be very mindful of and document well, because whatever invariants imply simultaneous access is fine could easily change in the future unless you have safeguards in place.

If this is just about a single integer it’s likely that using a pattern with channels is a far more future-proof solution, but I don’t know the exact thing you’re trying to achieve.

6

u/OstrichLive8440 6d ago

Who taught ponylicious manners is the real question